From 47582b162368e8edf7a3b11c13b8e9dabc5f0a26 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 31 Mar 2022 11:56:14 -0700 Subject: 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 --- src/clean.cmd | 1 + src/dtf/SfxCA/ClrHost.cpp | 262 +++++ src/dtf/SfxCA/EmbeddedUI.cpp | 281 +++++ src/dtf/SfxCA/EntryPoints.def | 140 +++ src/dtf/SfxCA/EntryPoints.h | 162 +++ src/dtf/SfxCA/Extract.cpp | 282 +++++ src/dtf/SfxCA/RemoteMsi.cpp | 629 +++++++++++ src/dtf/SfxCA/RemoteMsiSession.h | 898 +++++++++++++++ src/dtf/SfxCA/SfxCA.cpp | 363 ++++++ src/dtf/SfxCA/SfxCA.rc | 10 + src/dtf/SfxCA/SfxCA.vcxproj | 79 ++ src/dtf/SfxCA/SfxCA.vcxproj.filters | 62 ++ src/dtf/SfxCA/SfxUtil.cpp | 209 ++++ src/dtf/SfxCA/SfxUtil.h | 31 + src/dtf/SfxCA/precomp.cpp | 3 + src/dtf/SfxCA/precomp.h | 18 + src/dtf/SfxCA/sfxca_t.proj | 7 + .../WixToolset.Dtf.CustomAction.csproj | 17 + .../WixToolset.Dtf.CustomAction.nuspec | 32 + .../WixToolset.Dtf.CustomAction.targets | 121 ++ .../WixToolset.Dtf.CustomAction.v3.ncrunchproject | 5 + .../WixToolset.Dtf.MSBuild.csproj | 40 - .../WixToolset.Dtf.MSBuild.nuspec | 18 - .../build/WixToolset.Dtf.MSBuild.props | 8 - .../WixToolset.Dtf.MSBuild/tools/wix.ca.targets | 123 --- src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs | 710 ++++++++++++ .../MakeSfxCA.exe.manifest | 11 + .../WixToolset.Dtf.MakeSfxCA.csproj | 19 + src/dtf/WixToolset.Dtf.MakeSfxCA/app.config | 7 + .../WixToolset.Dtf.Resources/ResourceCollection.cs | 36 +- .../WixToolsetTests.Dtf.Compression.Cab/CabTest.cs | 1165 -------------------- .../WixToolsetTests.Dtf.Compression.Cab.csproj | 43 - .../WixToolsetTests.Dtf.Compression.Zip.csproj | 41 - .../WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs | 518 --------- .../CompressionTestUtil.cs | 649 ----------- .../MisbehavingStreamContext.cs | 202 ---- .../OptionStreamContext.cs | 42 - .../WixToolsetTests.Dtf.Compression.csproj | 36 - .../CustomActionTest.cs | 206 ---- ...Tests.Dtf.WindowsInstaller.CustomActions.csproj | 32 - .../LinqTest.cs | 509 --------- ...ixToolsetTests.Dtf.WindowsInstaller.Linq.csproj | 42 - .../EmbeddedExternalUI.cs | 173 --- .../WixToolsetTests.Dtf.WindowsInstaller/Schema.cs | 238 ---- .../WindowsInstallerTest.cs | 409 ------- .../WindowsInstallerTransactions.cs | 161 --- .../WindowsInstallerUtils.cs | 174 --- .../WixToolsetTests.Dtf.WindowsInstaller.csproj | 39 - src/dtf/dtf.cmd | 2 + src/dtf/dtf.sln | 102 +- .../WixToolsetTests.Dtf.Compression.Cab/CabTest.cs | 1165 ++++++++++++++++++++ .../WixToolsetTests.Dtf.Compression.Cab.csproj | 43 + .../WixToolsetTests.Dtf.Compression.Zip.csproj | 41 + .../WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs | 518 +++++++++ .../CompressionTestUtil.cs | 649 +++++++++++ .../MisbehavingStreamContext.cs | 202 ++++ .../OptionStreamContext.cs | 42 + .../WixToolsetTests.Dtf.Compression.csproj | 36 + .../CustomActionTest.cs | 206 ++++ ...Tests.Dtf.WindowsInstaller.CustomActions.csproj | 32 + .../LinqTest.cs | 509 +++++++++ ...ixToolsetTests.Dtf.WindowsInstaller.Linq.csproj | 42 + .../EmbeddedExternalUI.cs | 173 +++ .../WixToolsetTests.Dtf.WindowsInstaller/Schema.cs | 238 ++++ .../WindowsInstallerTest.cs | 409 +++++++ .../WindowsInstallerTransactions.cs | 161 +++ .../WindowsInstallerUtils.cs | 174 +++ .../WixToolsetTests.Dtf.WindowsInstaller.csproj | 39 + .../SetBuildNumber/Directory.Packages.props.pp | 1 + .../WixBuildFinalize/WixBuildFinalize.proj | 2 +- src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs | 5 - src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj | 56 - .../Dtf/EmbeddedUI/InstallProgressCounter.cs | 176 --- src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs | 132 --- src/samples/Dtf/EmbeddedUI/SetupWizard.xaml | 17 - src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs | 111 -- src/samples/Dtf/ManagedCA/AssemblyInfo.cs | 5 - src/samples/Dtf/ManagedCA/ManagedCA.csproj | 33 - src/samples/Dtf/ManagedCA/SampleCAs.cs | 127 --- src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs | 711 ------------ src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj | 27 - .../Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest | 20 - src/samples/Dtf/Tools/MakeSfxCA/app.config | 10 - src/samples/Dtf/Tools/SfxCA/ClrHost.cpp | 262 ----- src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp | 281 ----- src/samples/Dtf/Tools/SfxCA/EntryPoints.def | 140 --- src/samples/Dtf/Tools/SfxCA/EntryPoints.h | 162 --- src/samples/Dtf/Tools/SfxCA/Extract.cpp | 282 ----- src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp | 629 ----------- src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h | 898 --------------- src/samples/Dtf/Tools/SfxCA/SfxCA.cpp | 363 ------ src/samples/Dtf/Tools/SfxCA/SfxCA.rc | 10 - src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj | 67 -- src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters | 62 -- src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp | 209 ---- src/samples/Dtf/Tools/SfxCA/SfxUtil.h | 31 - src/samples/Dtf/Tools/SfxCA/packages.config | 4 - src/samples/Dtf/Tools/SfxCA/precomp.cpp | 3 - src/samples/Dtf/Tools/SfxCA/precomp.h | 18 - src/samples/Dtf/Tools/Tools.proj | 15 - src/test/dtf/Directory.Build.props | 11 + src/test/dtf/Directory.Build.targets | 6 + src/test/dtf/DtfE2ETests.sln | 28 + src/test/dtf/EmbeddedUI/AssemblyInfo.cs | 3 + src/test/dtf/EmbeddedUI/EmbeddedUI.csproj | 49 + src/test/dtf/EmbeddedUI/InstallProgressCounter.cs | 174 +++ src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs | 130 +++ src/test/dtf/EmbeddedUI/SetupWizard.xaml | 17 + src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs | 109 ++ src/test/dtf/SampleCA/SampleCA.cs | 125 +++ src/test/dtf/SampleCA/SampleCA.csproj | 10 + src/test/test.cmd | 2 + src/wix/WixToolset.Sdk/WixToolset.Sdk.csproj | 1 - src/wix/WixToolset.Sdk/tools/wix.ca.targets | 123 --- 114 files changed, 9787 insertions(+), 9916 deletions(-) create mode 100644 src/dtf/SfxCA/ClrHost.cpp create mode 100644 src/dtf/SfxCA/EmbeddedUI.cpp create mode 100644 src/dtf/SfxCA/EntryPoints.def create mode 100644 src/dtf/SfxCA/EntryPoints.h create mode 100644 src/dtf/SfxCA/Extract.cpp create mode 100644 src/dtf/SfxCA/RemoteMsi.cpp create mode 100644 src/dtf/SfxCA/RemoteMsiSession.h create mode 100644 src/dtf/SfxCA/SfxCA.cpp create mode 100644 src/dtf/SfxCA/SfxCA.rc create mode 100644 src/dtf/SfxCA/SfxCA.vcxproj create mode 100644 src/dtf/SfxCA/SfxCA.vcxproj.filters create mode 100644 src/dtf/SfxCA/SfxUtil.cpp create mode 100644 src/dtf/SfxCA/SfxUtil.h create mode 100644 src/dtf/SfxCA/precomp.cpp create mode 100644 src/dtf/SfxCA/precomp.h create mode 100644 src/dtf/SfxCA/sfxca_t.proj create mode 100644 src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.csproj create mode 100644 src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.nuspec create mode 100644 src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets create mode 100644 src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.v3.ncrunchproject delete mode 100644 src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj delete mode 100644 src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec delete mode 100644 src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props delete mode 100644 src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets create mode 100644 src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs create mode 100644 src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest create mode 100644 src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj create mode 100644 src/dtf/WixToolset.Dtf.MakeSfxCA/app.config delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs delete mode 100644 src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs create mode 100644 src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj delete mode 100644 src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs delete mode 100644 src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj delete mode 100644 src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs delete mode 100644 src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs delete mode 100644 src/samples/Dtf/EmbeddedUI/SetupWizard.xaml delete mode 100644 src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs delete mode 100644 src/samples/Dtf/ManagedCA/AssemblyInfo.cs delete mode 100644 src/samples/Dtf/ManagedCA/ManagedCA.csproj delete mode 100644 src/samples/Dtf/ManagedCA/SampleCAs.cs delete mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs delete mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj delete mode 100644 src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest delete mode 100644 src/samples/Dtf/Tools/MakeSfxCA/app.config delete mode 100644 src/samples/Dtf/Tools/SfxCA/ClrHost.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/EntryPoints.def delete mode 100644 src/samples/Dtf/Tools/SfxCA/EntryPoints.h delete mode 100644 src/samples/Dtf/Tools/SfxCA/Extract.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.rc delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/SfxUtil.h delete mode 100644 src/samples/Dtf/Tools/SfxCA/packages.config delete mode 100644 src/samples/Dtf/Tools/SfxCA/precomp.cpp delete mode 100644 src/samples/Dtf/Tools/SfxCA/precomp.h delete mode 100644 src/samples/Dtf/Tools/Tools.proj create mode 100644 src/test/dtf/Directory.Build.props create mode 100644 src/test/dtf/Directory.Build.targets create mode 100644 src/test/dtf/DtfE2ETests.sln create mode 100644 src/test/dtf/EmbeddedUI/AssemblyInfo.cs create mode 100644 src/test/dtf/EmbeddedUI/EmbeddedUI.csproj create mode 100644 src/test/dtf/EmbeddedUI/InstallProgressCounter.cs create mode 100644 src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs create mode 100644 src/test/dtf/EmbeddedUI/SetupWizard.xaml create mode 100644 src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs create mode 100644 src/test/dtf/SampleCA/SampleCA.cs create mode 100644 src/test/dtf/SampleCA/SampleCA.csproj delete mode 100644 src/wix/WixToolset.Sdk/tools/wix.ca.targets diff --git a/src/clean.cmd b/src/clean.cmd index 94f9a618..5bedadbd 100644 --- a/src/clean.cmd +++ b/src/clean.cmd @@ -29,6 +29,7 @@ if exist "%_NUGET_CACHE%\wixtoolset.data" rd /s/q "%_NUGET_CACHE%\wixtoolset.dat if exist "%_NUGET_CACHE%\wixtoolset.dependency.wixext" rd /s/q "%_NUGET_CACHE%\wixtoolset.dependency.wixext" if exist "%_NUGET_CACHE%\wixtoolset.dtf.compression" rd /s/q "%_NUGET_CACHE%\wixtoolset.dtf.compression" if exist "%_NUGET_CACHE%\wixtoolset.dtf.compression.cab" rd /s/q "%_NUGET_CACHE%\wixtoolset.dtf.compression.cab" +if exist "%_NUGET_CACHE%\wixtoolset.dtf.customaction" rd /s/q "%_NUGET_CACHE%\wixtoolset.dtf.customaction" if exist "%_NUGET_CACHE%\wixtoolset.dtf.resources" rd /s/q "%_NUGET_CACHE%\wixtoolset.dtf.resources" if exist "%_NUGET_CACHE%\wixtoolset.dtf.windowsinstaller" rd /s/q "%_NUGET_CACHE%\wixtoolset.dtf.windowsinstaller" if exist "%_NUGET_CACHE%\wixtoolset.dutil" rd /s/q "%_NUGET_CACHE%\wixtoolset.dutil" 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); + +//--------------------------------------------------------------------- +// CLR HOSTING +//--------------------------------------------------------------------- + +/// +/// Binds to the CLR after determining the appropriate version. +/// +/// Handle to the installer session, +/// used just for logging. +/// Specific version of the CLR to load. +/// If null, then the config file and/or primary assembly are +/// used to determine the version. +/// XML .config file which may contain +/// a startup section to direct which version of the CLR to use. +/// May be NULL. +/// Assembly to be used to determine +/// the version of the CLR in the absence of other configuration. +/// May be NULL. +/// Returned runtime host interface. +/// True if the CLR was loaded successfully, false if +/// there was some error. +/// +/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or +/// does not contain any version configuration, the CLR will not be loaded. +/// +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost) +{ + typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, + LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, + LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, + LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); + typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, + DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv); + + HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll"); + if (hmodMscoree == NULL) + { + Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action " + L"requires the .NET Framework to be installed.", GetLastError()); + return false; + } + PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo) + GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo"); + PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx) + GetProcAddress(hmodMscoree, "CorBindToRuntimeEx"); + if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL) + { + Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action " + L"requires the .NET Framework to be installed.", GetLastError()); + FreeLibrary(hmodMscoree); + return false; + } + + wchar_t szClrVersion[20]; + HRESULT hr; + + if (szVersion != NULL && szVersion[0] != L'\0') + { + wcsncpy_s(szClrVersion, 20, szVersion, 20); + } + else + { + wchar_t szVersionDir[MAX_PATH]; + hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL, + szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL); + if (FAILED(hr)) + { + Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr); + Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or " + L"that there is a matching supportedRuntime element in CustomAction.config. " + L"If you are binding to .NET 4 or greater add " + L"useLegacyV2RuntimeActivationPolicy=true to the element."); + FreeLibrary(hmodMscoree); + return false; + } + } + + Log(hSession, L"Binding to CLR version %s", szClrVersion); + + ICorRuntimeHost* pHost; + hr = pCorBindToRuntimeEx(szClrVersion, NULL, + STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, + CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost); + if (FAILED(hr)) + { + Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr); + FreeLibrary(hmodMscoree); + return false; + } + hr = pHost->Start(); + if (FAILED(hr)) + { + Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr); + pHost->Release(); + FreeLibrary(hmodMscoree); + return false; + } + *ppHost = pHost; + FreeLibrary(hmodMscoree); + return true; +} + +/// +/// Creates a new CLR application domain. +/// +/// Handle to the installer session, +/// used just for logging +/// Interface to the runtime host where the +/// app domain will be created. +/// Name of the app domain to create. +/// Application base directory path, where +/// the app domain will look first to load its assemblies. +/// Optional XML .config file containing any +/// configuration for thae app domain. +/// Returned app domain interface. +/// True if the app domain was created successfully, false if +/// there was some error. +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, + const wchar_t* szName, const wchar_t* szAppBase, + const wchar_t* szConfigFile, _AppDomain** ppAppDomain) +{ + IUnknown* punkAppDomainSetup = NULL; + IAppDomainSetup* pAppDomainSetup = NULL; + HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup); + if (SUCCEEDED(hr)) + { + hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup); + punkAppDomainSetup->Release(); + } + if (FAILED(hr)) + { + Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr); + return false; + } + + const wchar_t* szUrlPrefix = L"file:///"; + size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase); + wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t)); + if (szApplicationBase == NULL) hr = E_OUTOFMEMORY; + else + { + StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix); + StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase); + BSTR bstrApplicationBase = SysAllocString(szApplicationBase); + if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase); + SysFreeString(bstrApplicationBase); + } + } + + if (SUCCEEDED(hr) && szConfigFile != NULL) + { + BSTR bstrConfigFile = SysAllocString(szConfigFile); + if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile); + SysFreeString(bstrConfigFile); + } + } + + if (FAILED(hr)) + { + Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr); + pAppDomainSetup->Release(); + return false; + } + + IUnknown* punkAppDomain; + hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain); + pAppDomainSetup->Release(); + if (SUCCEEDED(hr)) + { + hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain); + punkAppDomain->Release(); + } + + if (FAILED(hr)) + { + Log(hSession, L"Failed to create app domain. Error code 0x%X", hr); + return false; + } + + return true; +} + +/// +/// Locates a specific method in a specific class and assembly. +/// +/// Handle to the installer session, +/// used just for logging +/// Application domain in which to +/// load assemblies. +/// Display name of the assembly +/// containing the method. +/// Fully-qualified name of the class +/// containing the method. +/// Name of the method. +/// Returned method interface. +/// True if the method was located, otherwise false. +/// Only public static methods are searched. Method +/// parameter types are not considered; if there are multiple +/// matching methods with different parameters, an error results. +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szAssembly, const wchar_t* szClass, + const wchar_t* szMethod, _MethodInfo** ppMethod) +{ + HRESULT hr; + _Assembly* pAssembly = NULL; + BSTR bstrAssemblyName = SysAllocString(szAssembly); + if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly); + SysFreeString(bstrAssemblyName); + } + if (FAILED(hr)) + { + Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr); + return false; + } + + _Type* pType = NULL; + BSTR bstrClass = SysAllocString(szClass); + if (bstrClass == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pAssembly->GetType_2(bstrClass, &pType); + SysFreeString(bstrClass); + } + pAssembly->Release(); + if (FAILED(hr) || pType == NULL) + { + Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr); + return false; + } + + BSTR bstrMethod = SysAllocString(szMethod); + if (bstrMethod == NULL) hr = E_OUTOFMEMORY; + else + { + hr = pType->GetMethod_2(bstrMethod, + (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod); + SysFreeString(bstrMethod); + } + pType->Release(); + if (FAILED(hr) || *ppMethod == NULL) + { + Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr); + return false; + } + return true; +} 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#include "SfxUtil.h" + +// Globals for keeping track of things across UI messages. +static const wchar_t* g_szWorkingDir; +static ICorRuntimeHost* g_pClrHost; +static _AppDomain* g_pAppDomain; +static _MethodInfo* g_pProcessMessageMethod; +static _MethodInfo* g_pShutdownMethod; + +// Reserve extra space for strings to be replaced at build time. +#define NULLSPACE \ +L"\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" \ +L"\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" \ +L"\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" \ +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +// Prototypes for local functions. +// See the function definitions for comments. + +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, + const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); + +/// +/// First entry-point for the UI DLL when loaded and called by MSI. +/// Extracts the payload, hosts the CLR, and invokes the managed +/// initialize method. +/// +/// Handle to the installer session, +/// used for logging errors and to be passed on to the managed initialize method. +/// Path the directory where resources from the MsiEmbeddedUI table +/// have been extracted, and where additional payload from this package will be extracted. +/// MSI install UI level passed to and returned from +/// the managed initialize method. +extern "C" +UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) +{ + // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. + UINT uiResult = INSTALLUILEVEL_BASIC; + + const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; + + g_szWorkingDir = szResourcePath; + + wchar_t szModule[MAX_PATH]; + DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); + if (cchCopied == 0) + { + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); + return uiResult; + } + else if (cchCopied == MAX_PATH - 1) + { + Log(hSession, L"Failed to get module path -- path is too long."); + return uiResult; + } + + Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); + int err = ExtractCabinet(szModule, g_szWorkingDir); + if (err != 0) + { + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); + Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " + L"any file contained in the embedded UI package."); + return uiResult; + } + + wchar_t szConfigFilePath[MAX_PATH + 20]; + StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); + + const wchar_t* szConfigFile = szConfigFilePath; + if (!PathFileExists(szConfigFilePath)) + { + szConfigFile = NULL; + } + + wchar_t szWIAssembly[MAX_PATH + 50]; + StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); + + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) + { + if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, + szConfigFile, &g_pAppDomain)) + { + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; + const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; + const wchar_t* szInitMethod = L"Initialize"; + const wchar_t* szProcessMessageMethod = L"ProcessMessage"; + const wchar_t* szShutdownMethod = L"Shutdown"; + + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && + GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szShutdownMethod, &g_pShutdownMethod)) + { + _MethodInfo* pInitMethod; + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, + szProxyClass, szInitMethod, &pInitMethod)) + { + bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); + pInitMethod->Release(); + if (invokeSuccess) + { + if (uiResult == 0) + { + return ERROR_SUCCESS; + } + else if (uiResult == ERROR_INSTALL_USEREXIT) + { + // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. + // So return success here and then IDCANCEL on the next progress message. + uiResult = 0; + *pdwInternalUILevel = INSTALLUILEVEL_NONE; + Log(hSession, L"Initialization canceled by user."); + } + } + } + } + + g_pProcessMessageMethod->Release(); + g_pProcessMessageMethod = NULL; + g_pShutdownMethod->Release(); + g_pShutdownMethod = NULL; + + g_pClrHost->UnloadDomain(g_pAppDomain); + g_pAppDomain->Release(); + g_pAppDomain = NULL; + } + g_pClrHost->Stop(); + g_pClrHost->Release(); + g_pClrHost = NULL; + } + + return uiResult; +} + +/// +/// Entry-point for UI progress messages received from the MSI engine during an active installation. +/// Forwards the progress messages to the managed handler method and returns its result. +/// +extern "C" +INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) +{ + if (g_pProcessMessageMethod == NULL) + { + // Initialization was canceled. + return IDCANCEL; + } + + VARIANT vResult; + VariantInit(&vResult); + + VARIANT vNull; + vNull.vt = VT_EMPTY; + + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); + VARIANT vMessageType; + vMessageType.vt = VT_I4; + vMessageType.lVal = (LONG) uiMessageType; + LONG index = 0; + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); + if (FAILED(hr)) goto LExit; + VARIANT vRecord; + vRecord.vt = VT_I4; + vRecord.lVal = (LONG) hRecord; + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vRecord); + if (FAILED(hr)) goto LExit; + + hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + if (SUCCEEDED(hr)) + { + return vResult.intVal; + } + else + { + return -1; + } +} + +/// +/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. +/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. +/// +extern "C" +DWORD __stdcall ShutdownEmbeddedUI() +{ + if (g_pShutdownMethod != NULL) + { + VARIANT vNull; + vNull.vt = VT_EMPTY; + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); + g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); + SafeArrayDestroy(saArgs); + + g_pClrHost->UnloadDomain(g_pAppDomain); + g_pAppDomain->Release(); + g_pClrHost->Stop(); + g_pClrHost->Release(); + } + + return 0; +} + +/// +/// Loads and invokes the managed portion of the proxy. +/// +/// Managed initialize method to be invoked. +/// Handle to the installer session, +/// used for logging errors and to be passed on to the managed initialize method. +/// Name of the UI class to be loaded. +/// This must be of the form: AssemblyName!Namespace.Class +/// MSI install UI level passed to and returned from +/// the managed initialize method. +/// Return value of the invoked initialize method. +/// True if the managed proxy was invoked successfully, or an +/// error code if there was some error. Note the initialize method itself may +/// return an error via puiResult while this method still returns true +/// since the invocation was successful. +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) +{ + VARIANT vResult; + VariantInit(&vResult); + + VARIANT vNull; + vNull.vt = VT_EMPTY; + + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); + VARIANT vSessionHandle; + vSessionHandle.vt = VT_I4; + vSessionHandle.lVal = (LONG) hSession; + LONG index = 0; + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); + if (FAILED(hr)) goto LExit; + VARIANT vEntryPoint; + vEntryPoint.vt = VT_BSTR; + vEntryPoint.bstrVal = SysAllocString(szClassName); + if (vEntryPoint.bstrVal == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); + if (FAILED(hr)) goto LExit; + VARIANT vUILevel; + vUILevel.vt = VT_I4; + vUILevel.ulVal = *pdwInternalUILevel; + index = 2; + hr = SafeArrayPutElement(saArgs, &index, &vUILevel); + if (FAILED(hr)) goto LExit; + + hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + if (SUCCEEDED(hr)) + { + *puiResult = (UINT) vResult.lVal; + if ((*puiResult & 0xFFFF) == 0) + { + // Due to interop limitations, the successful resulting UILevel is returned + // as the high-word of the return value instead of via a ref parameter. + *pdwInternalUILevel = *puiResult >> 16; + *puiResult = 0; + } + return true; + } + else + { + Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); + return false; + } +} 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 @@ +; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +LIBRARY "SfxCA" + +EXPORTS + +CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 +CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 +CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 +CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 +CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 +CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 +CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 +CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 +CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 +CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 +CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 +CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 +CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 +CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 +CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 +CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 +CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 +CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 +CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 +CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 +CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 +CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 +CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 +CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 +CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 +CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 +CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 +CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 +CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 +CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 +CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 +CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 +CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 +CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 +CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 +CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 +CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 +CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 +CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 +CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 +CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 +CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 +CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 +CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 +CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 +CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 +CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 +CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 +CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 +CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 +CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 +CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 +CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 +CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 +CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 +CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 +CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 +CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 +CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 +CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 +CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 +CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 +CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 +CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 +CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 +CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 +CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 +CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 +CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 +CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 +CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 +CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 +CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 +CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 +CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 +CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 +CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 +CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 +CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 +CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 +CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 +CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 +CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 +CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 +CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 +CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 +CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 +CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 +CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 +CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 +CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 +CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 +CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 +CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 +CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 +CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 +CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 +CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 +CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 +CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 +CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 +CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 +CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 +CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 +CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 +CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 +CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 +CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 +CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 +CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 +CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 +CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 +CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 +CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 +CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 +CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 +CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 +CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 +CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 +CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 +CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 +CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 +CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 +CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 +CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 +CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 +CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 +CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 + +zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc +zzzInitializeEmbeddedUI=InitializeEmbeddedUI +zzzEmbeddedUIHandler=EmbeddedUIHandler +zzzShutdownEmbeddedUI=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 @@ +// 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. + +int InvokeCustomAction(MSIHANDLE hSession, + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint); + +/// +/// Macro for defining and exporting a custom action entrypoint. +/// +/// Name of the entrypoint as exported from +/// the DLL. +/// Path to the managed custom action method, +/// in the form: "AssemblyName!Namespace.Class.Method" +/// +/// To prevent the exported name from being decorated, add +/// /EXPORT:name to the linker options for every entrypoint. +/// +#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \ + name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); } + +// TEMPLATE ENTRYPOINTS +// To be edited by the MakeSfxCA tool. + +#define NULLSPACE \ +L"\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" \ +L"\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" \ +L"\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" \ +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \ + CustomActionEntryPoint##id##, \ + L"CustomActionEntryPoint" sid NULLSPACE) + +TEMPLATE_CA_ENTRYPOINT(000,L"000"); +TEMPLATE_CA_ENTRYPOINT(001,L"001"); +TEMPLATE_CA_ENTRYPOINT(002,L"002"); +TEMPLATE_CA_ENTRYPOINT(003,L"003"); +TEMPLATE_CA_ENTRYPOINT(004,L"004"); +TEMPLATE_CA_ENTRYPOINT(005,L"005"); +TEMPLATE_CA_ENTRYPOINT(006,L"006"); +TEMPLATE_CA_ENTRYPOINT(007,L"007"); +TEMPLATE_CA_ENTRYPOINT(008,L"008"); +TEMPLATE_CA_ENTRYPOINT(009,L"009"); +TEMPLATE_CA_ENTRYPOINT(010,L"010"); +TEMPLATE_CA_ENTRYPOINT(011,L"011"); +TEMPLATE_CA_ENTRYPOINT(012,L"012"); +TEMPLATE_CA_ENTRYPOINT(013,L"013"); +TEMPLATE_CA_ENTRYPOINT(014,L"014"); +TEMPLATE_CA_ENTRYPOINT(015,L"015"); +TEMPLATE_CA_ENTRYPOINT(016,L"016"); +TEMPLATE_CA_ENTRYPOINT(017,L"017"); +TEMPLATE_CA_ENTRYPOINT(018,L"018"); +TEMPLATE_CA_ENTRYPOINT(019,L"019"); +TEMPLATE_CA_ENTRYPOINT(020,L"020"); +TEMPLATE_CA_ENTRYPOINT(021,L"021"); +TEMPLATE_CA_ENTRYPOINT(022,L"022"); +TEMPLATE_CA_ENTRYPOINT(023,L"023"); +TEMPLATE_CA_ENTRYPOINT(024,L"024"); +TEMPLATE_CA_ENTRYPOINT(025,L"025"); +TEMPLATE_CA_ENTRYPOINT(026,L"026"); +TEMPLATE_CA_ENTRYPOINT(027,L"027"); +TEMPLATE_CA_ENTRYPOINT(028,L"028"); +TEMPLATE_CA_ENTRYPOINT(029,L"029"); +TEMPLATE_CA_ENTRYPOINT(030,L"030"); +TEMPLATE_CA_ENTRYPOINT(031,L"031"); +TEMPLATE_CA_ENTRYPOINT(032,L"032"); +TEMPLATE_CA_ENTRYPOINT(033,L"033"); +TEMPLATE_CA_ENTRYPOINT(034,L"034"); +TEMPLATE_CA_ENTRYPOINT(035,L"035"); +TEMPLATE_CA_ENTRYPOINT(036,L"036"); +TEMPLATE_CA_ENTRYPOINT(037,L"037"); +TEMPLATE_CA_ENTRYPOINT(038,L"038"); +TEMPLATE_CA_ENTRYPOINT(039,L"039"); +TEMPLATE_CA_ENTRYPOINT(040,L"040"); +TEMPLATE_CA_ENTRYPOINT(041,L"041"); +TEMPLATE_CA_ENTRYPOINT(042,L"042"); +TEMPLATE_CA_ENTRYPOINT(043,L"043"); +TEMPLATE_CA_ENTRYPOINT(044,L"044"); +TEMPLATE_CA_ENTRYPOINT(045,L"045"); +TEMPLATE_CA_ENTRYPOINT(046,L"046"); +TEMPLATE_CA_ENTRYPOINT(047,L"047"); +TEMPLATE_CA_ENTRYPOINT(048,L"048"); +TEMPLATE_CA_ENTRYPOINT(049,L"049"); +TEMPLATE_CA_ENTRYPOINT(050,L"050"); +TEMPLATE_CA_ENTRYPOINT(051,L"051"); +TEMPLATE_CA_ENTRYPOINT(052,L"052"); +TEMPLATE_CA_ENTRYPOINT(053,L"053"); +TEMPLATE_CA_ENTRYPOINT(054,L"054"); +TEMPLATE_CA_ENTRYPOINT(055,L"055"); +TEMPLATE_CA_ENTRYPOINT(056,L"056"); +TEMPLATE_CA_ENTRYPOINT(057,L"057"); +TEMPLATE_CA_ENTRYPOINT(058,L"058"); +TEMPLATE_CA_ENTRYPOINT(059,L"059"); +TEMPLATE_CA_ENTRYPOINT(060,L"060"); +TEMPLATE_CA_ENTRYPOINT(061,L"061"); +TEMPLATE_CA_ENTRYPOINT(062,L"062"); +TEMPLATE_CA_ENTRYPOINT(063,L"063"); +TEMPLATE_CA_ENTRYPOINT(064,L"064"); +TEMPLATE_CA_ENTRYPOINT(065,L"065"); +TEMPLATE_CA_ENTRYPOINT(066,L"066"); +TEMPLATE_CA_ENTRYPOINT(067,L"067"); +TEMPLATE_CA_ENTRYPOINT(068,L"068"); +TEMPLATE_CA_ENTRYPOINT(069,L"069"); +TEMPLATE_CA_ENTRYPOINT(070,L"070"); +TEMPLATE_CA_ENTRYPOINT(071,L"071"); +TEMPLATE_CA_ENTRYPOINT(072,L"072"); +TEMPLATE_CA_ENTRYPOINT(073,L"073"); +TEMPLATE_CA_ENTRYPOINT(074,L"074"); +TEMPLATE_CA_ENTRYPOINT(075,L"075"); +TEMPLATE_CA_ENTRYPOINT(076,L"076"); +TEMPLATE_CA_ENTRYPOINT(077,L"077"); +TEMPLATE_CA_ENTRYPOINT(078,L"078"); +TEMPLATE_CA_ENTRYPOINT(079,L"079"); +TEMPLATE_CA_ENTRYPOINT(080,L"080"); +TEMPLATE_CA_ENTRYPOINT(081,L"081"); +TEMPLATE_CA_ENTRYPOINT(082,L"082"); +TEMPLATE_CA_ENTRYPOINT(083,L"083"); +TEMPLATE_CA_ENTRYPOINT(084,L"084"); +TEMPLATE_CA_ENTRYPOINT(085,L"085"); +TEMPLATE_CA_ENTRYPOINT(086,L"086"); +TEMPLATE_CA_ENTRYPOINT(087,L"087"); +TEMPLATE_CA_ENTRYPOINT(088,L"088"); +TEMPLATE_CA_ENTRYPOINT(089,L"089"); +TEMPLATE_CA_ENTRYPOINT(090,L"090"); +TEMPLATE_CA_ENTRYPOINT(091,L"091"); +TEMPLATE_CA_ENTRYPOINT(092,L"092"); +TEMPLATE_CA_ENTRYPOINT(093,L"093"); +TEMPLATE_CA_ENTRYPOINT(094,L"094"); +TEMPLATE_CA_ENTRYPOINT(095,L"095"); +TEMPLATE_CA_ENTRYPOINT(096,L"096"); +TEMPLATE_CA_ENTRYPOINT(097,L"097"); +TEMPLATE_CA_ENTRYPOINT(098,L"098"); +TEMPLATE_CA_ENTRYPOINT(099,L"099"); +TEMPLATE_CA_ENTRYPOINT(100,L"100"); +TEMPLATE_CA_ENTRYPOINT(101,L"101"); +TEMPLATE_CA_ENTRYPOINT(102,L"102"); +TEMPLATE_CA_ENTRYPOINT(103,L"103"); +TEMPLATE_CA_ENTRYPOINT(104,L"104"); +TEMPLATE_CA_ENTRYPOINT(105,L"105"); +TEMPLATE_CA_ENTRYPOINT(106,L"106"); +TEMPLATE_CA_ENTRYPOINT(107,L"107"); +TEMPLATE_CA_ENTRYPOINT(108,L"108"); +TEMPLATE_CA_ENTRYPOINT(109,L"109"); +TEMPLATE_CA_ENTRYPOINT(110,L"110"); +TEMPLATE_CA_ENTRYPOINT(111,L"111"); +TEMPLATE_CA_ENTRYPOINT(112,L"112"); +TEMPLATE_CA_ENTRYPOINT(113,L"113"); +TEMPLATE_CA_ENTRYPOINT(114,L"114"); +TEMPLATE_CA_ENTRYPOINT(115,L"115"); +TEMPLATE_CA_ENTRYPOINT(116,L"116"); +TEMPLATE_CA_ENTRYPOINT(117,L"117"); +TEMPLATE_CA_ENTRYPOINT(118,L"118"); +TEMPLATE_CA_ENTRYPOINT(119,L"119"); +TEMPLATE_CA_ENTRYPOINT(120,L"120"); +TEMPLATE_CA_ENTRYPOINT(121,L"121"); +TEMPLATE_CA_ENTRYPOINT(122,L"122"); +TEMPLATE_CA_ENTRYPOINT(123,L"123"); +TEMPLATE_CA_ENTRYPOINT(124,L"124"); +TEMPLATE_CA_ENTRYPOINT(125,L"125"); +TEMPLATE_CA_ENTRYPOINT(126,L"126"); +TEMPLATE_CA_ENTRYPOINT(127,L"127"); + +// 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +//--------------------------------------------------------------------- +// CABINET EXTRACTION +//--------------------------------------------------------------------- + +// Globals make this code unsuited for multhreaded use, +// but FDI doesn't provide any other way to pass context. + +// Handle to the FDI (cab extraction) engine. Need access to this in a callback. +static HFDI g_hfdi; + +// FDI is not unicode-aware, so avoid passing these paths through the callbacks. +static const wchar_t* g_szExtractDir; +static const wchar_t* g_szCabFile; + +// Offset into the source file where the cabinet really starts. +// Used to trick FDI into extracting from a concatenated cabinet. +static int g_lCabOffset; + +// Use the secure CRT version of _wsopen if available. +#ifdef __GOT_SECURE_LIB__ +#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) +#else +#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) +#endif + +/// +/// FDI callback to open a cabinet file. +/// +/// Name of the file to be opened. This parameter +/// is ignored since with our limited use this method is only ever called +/// to open the main cabinet file. +/// Type of operations allowed. +/// Permission setting. +/// Integer file handle, or -1 if the file could not be opened. +/// +/// To support reading from a cabinet that is concatenated onto +/// another file, this function first searches for the offset of the cabinet, +/// then saves that offset for use in recalculating later seeks. +/// +static FNOPEN(CabOpen) +{ + UNREFERENCED_PARAMETER(pszFile); + int hf; + _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); + if (hf != -1) + { + FDICABINETINFO cabInfo; + int length = _lseek(hf, 0, SEEK_END); + for(int offset = 0; offset < length; offset += 256) + { + if (_lseek(hf, offset, SEEK_SET) != offset) break; + if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) + { + g_lCabOffset = offset; + _lseek(hf, offset, SEEK_SET); + return hf; + } + } + _close(hf); + } + return -1; +} + +/// +/// FDI callback to seek within a file. +/// +/// File handle. +/// Seek distance +/// Whether to seek relative to the +/// beginning, current position, or end of the file. +/// Resultant position within the cabinet. +/// +/// To support reading from a cabinet that is concatenated onto +/// another file, this function recalculates seeks based on the +/// offset that was determined when the cabinet was opened. +/// +static FNSEEK(CabSeek) +{ + if (seektype == SEEK_SET) dist += g_lCabOffset; + int pos = _lseek((int) hf, dist, seektype); + pos -= g_lCabOffset; + return pos; +} + +/// +/// Ensures a directory and its parent directory path exists. +/// +/// Directory path, not including file name. +/// 0 if the directory exists or was successfully created, else nonzero. +/// +/// This function modifies characters in szDirPath, but always restores them +/// regardless of error condition. +/// +static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) +{ + int ret = 0; + if (!::CreateDirectoryW(szDirPath, NULL)) + { + UINT err = ::GetLastError(); + if (err != ERROR_ALREADY_EXISTS) + { + // Directory creation failed for some reason other than already existing. + // Try to create the parent directory first. + wchar_t* szLastSlash = NULL; + for (wchar_t* sz = szDirPath; *sz; sz++) + { + if (*sz == L'\\') + { + szLastSlash = sz; + } + } + if (szLastSlash) + { + // Temporarily take one directory off the path and recurse. + *szLastSlash = L'\0'; + ret = EnsureDirectoryExists(szDirPath); + *szLastSlash = L'\\'; + + // Try to create the directory if all parents are created. + if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) + { + err = ::GetLastError(); + if (err != ERROR_ALREADY_EXISTS) + { + ret = -1; + } + } + } + else + { + ret = -1; + } + } + } + return ret; +} + +/// +/// Ensures a file's directory and its parent directory path exists. +/// +/// Path including file name. +/// 0 if the file's directory exists or was successfully created, else nonzero. +/// +/// This function modifies characters in szFilePath, but always restores them +/// regardless of error condition. +/// +static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) +{ + int ret = 0; + wchar_t* szLastSlash = NULL; + for (wchar_t* sz = szFilePath; *sz; sz++) + { + if (*sz == L'\\') + { + szLastSlash = sz; + } + } + if (szLastSlash) + { + *szLastSlash = L'\0'; + ret = EnsureDirectoryExists(szFilePath); + *szLastSlash = L'\\'; + } + return ret; +} + +/// +/// FDI callback for handling files in the cabinet. +/// +/// Type of notification. +/// Structure containing data about the notification. +/// +/// Refer to fdi.h for more comments on this notification callback. +/// +static FNFDINOTIFY(CabNotification) +{ + // fdintCOPY_FILE: + // Called for each file that *starts* in the current cabinet, giving + // the client the opportunity to request that the file be copied or + // skipped. + // Entry: + // pfdin->psz1 = file name in cabinet + // pfdin->cb = uncompressed size of file + // pfdin->date = file date + // pfdin->time = file time + // pfdin->attribs = file attributes + // pfdin->iFolder = file's folder index + // Exit-Success: + // Return non-zero file handle for destination file; FDI writes + // data to this file use the PFNWRITE function supplied to FDICreate, + // and then calls fdintCLOSE_FILE_INFO to close the file and set + // the date, time, and attributes. + // Exit-Failure: + // Returns 0 => Skip file, do not copy + // Returns -1 => Abort FDICopy() call + if (fdint == fdintCOPY_FILE) + { + size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); + size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; + wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); + if (szFilePath == NULL) return -1; + StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); + StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); + MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, + szFilePath + cchFilePath - cchFile, (int) cchFile + 1); + int hf = -1; + if (EnsureFileDirectoryExists(szFilePath) == 0) + { + _wsopen__s(hf, szFilePath, + _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, + _SH_DENYWR, _S_IREAD | _S_IWRITE); + } + return hf; + } + + // fdintCLOSE_FILE_INFO: + // Called after all of the data has been written to a target file. + // This function must close the file and set the file date, time, + // and attributes. + // Entry: + // pfdin->psz1 = file name in cabinet + // pfdin->hf = file handle + // pfdin->date = file date + // pfdin->time = file time + // pfdin->attribs = file attributes + // pfdin->iFolder = file's folder index + // pfdin->cb = Run After Extract (0 - don't run, 1 Run) + // Exit-Success: + // Returns TRUE + // Exit-Failure: + // Returns FALSE, or -1 to abort + else if (fdint == fdintCLOSE_FILE_INFO) + { + _close((int) pfdin->hf); + return TRUE; + } + return 0; +} + +/// +/// Extracts all contents of a cabinet file to a directory. +/// +/// Path to the cabinet file to be extracted. +/// The cabinet may actually start at some offset within the file, +/// as long as that offset is a multiple of 256. +/// Directory where files are to be extracted. +/// This directory must already exist, but should be empty. +/// 0 if the cabinet was extracted successfully, +/// or an error code if any error occurred. +/// +/// The extraction will not overwrite any files in the destination +/// directory; extraction will be interrupted and fail if any files +/// with the same name already exist. +/// +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) +{ + ERF erf; + // Most of the FDI callbacks can be handled by existing CRT I/O functions. + // For our functionality we only need to handle the open and seek callbacks. + HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, + (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, + CabSeek, cpu80386, &erf); + if (hfdi != NULL) + { + g_hfdi = hfdi; + g_szCabFile = szCabFile; + g_szExtractDir = szExtractDir; + char szEmpty[1] = {0}; + if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) + { + FDIDestroy(hfdi); + return 0; + } + FDIDestroy(hfdi); + } + + return erf.erfOper; +} 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#include "RemoteMsiSession.h" + + +// +// Ensures that the request buffer is large enough to hold a request, +// reallocating the buffer if necessary. +// It will also reduce the buffer size if the previous allocation was very large. +// +static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired) +{ + // It will also reduce the buffer size if the previous allocation was very large. + if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf)) + { + if (*pszBuf != NULL) + { + SecureZeroMemory(*pszBuf, *pcchBuf); + delete[] *pszBuf; + } + + *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired); + *pszBuf = new wchar_t[*pcchBuf]; + + if (*pszBuf == NULL) + { + return ERROR_OUTOFMEMORY; + } + } + + return ERROR_SUCCESS; +} + +typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); +typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); +typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); +typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); +typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); +typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); +typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); +typedef 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); + +UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + int out1; + UINT ret = (UINT) func(in1, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + int in2 = pReq->fields[1].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int out1; + UINT ret = (UINT) func(in1, in2, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, in3, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int in4 = pReq->fields[3].iValue; + int out1; + UINT ret = (UINT) func(in1, in2, in3, in4, &out1); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + } + return ret; +} + +UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int out1, out2; + UINT ret = (UINT) func(in1, in2, &out1, &out2); + if (ret == 0) + { + pResp->fields[1].vt = VT_I4; + pResp->fields[1].iValue = out1; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = out2; + } + return ret; +} + +UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) +{ + int in1 = pReq->fields[0].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) +{ + int in1 = pReq->fields[0].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + MSIDBERROR ret = func(in1, szBuf, &cchValue); + if (ret == MSIDBERROR_MOREDATA) + { + if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue)) + { + ret = func(in1, szBuf, &cchValue); + } + } + if (ret != MSIDBERROR_MOREDATA) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) +{ + int in1 = pReq->fields[0].iValue; + int in2 = pReq->fields[1].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, szBuf, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + } + return ret; +} + +UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) +{ + int in1 = pReq->fields[0].iValue; + wchar_t* in2 = pReq->fields[1].szValue; + int in3 = pReq->fields[2].iValue; + int in4 = pReq->fields[3].iValue; + szBuf[0] = L'\0'; + DWORD cchValue = cchBuf; + int out2, out3; + UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); + if (ret == 0) + { + ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_LPWSTR; + pResp->fields[1].szValue = szBuf; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = out2; + pResp->fields[3].vt = VT_I4; + pResp->fields[3].iValue = out3; + } + return ret; +} + +void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp) +{ + SecureZeroMemory(pResp, sizeof(RequestData)); + + UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024); + + if (0 == ret) + { + switch (id) + { + case RemoteMsiSession::EndSession: + { + this->ExitCode = pReq->fields[0].iValue; + } + break; + case RemoteMsiSession::MsiCloseHandle: + { + MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiCloseHandle(h); + } + break; + case RemoteMsiSession::MsiProcessMessage: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; + ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord); + } + break; + case RemoteMsiSession::MsiGetProperty: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiSetProperty: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szName = pReq->fields[1].szValue; + const wchar_t* szValue = pReq->fields[2].szValue; + ret = ::MsiSetProperty(hInstall, szName, szValue); + } + break; + case RemoteMsiSession::MsiCreateRecord: + { + UINT cParams = pReq->fields[0].uiValue; + ret = ::MsiCreateRecord(cParams); + } + break; + case RemoteMsiSession::MsiRecordGetFieldCount: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiRecordGetFieldCount(hRecord); + } + break; + case RemoteMsiSession::MsiRecordGetInteger: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordGetInteger(hRecord, iField); + } + break; + case RemoteMsiSession::MsiRecordSetInteger: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + int iValue = pReq->fields[2].iValue; + ret = ::MsiRecordSetInteger(hRecord, iField, iValue); + } + break; + case RemoteMsiSession::MsiRecordGetString: + { + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiRecordSetString: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + const wchar_t* szValue = pReq->fields[2].szValue; + ret = ::MsiRecordSetString(hRecord, iField, szValue); + } + break; + case RemoteMsiSession::MsiRecordClearData: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiRecordClearData(hRecord); + } + break; + case RemoteMsiSession::MsiRecordIsNull: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordIsNull(hRecord, iField); + } + break; + case RemoteMsiSession::MsiFormatRecord: + { + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiGetActiveDatabase: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = (UINT) ::MsiGetActiveDatabase(hInstall); + } + break; + case RemoteMsiSession::MsiDatabaseOpenView: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp); + } + break; + case RemoteMsiSession::MsiViewExecute: + { + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue; + ret = ::MsiViewExecute(hView, hRecord); + } + break; + case RemoteMsiSession::MsiViewFetch: + { + ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp); + } + break; + case RemoteMsiSession::MsiViewModify: + { + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; + MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue; + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; + ret = ::MsiViewModify(hView, eModifyMode, hRecord); + } + break; + case RemoteMsiSession::MsiViewGetError: + { + ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiViewGetColumnInfo: + { + ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp); + } + break; + case RemoteMsiSession::MsiDatabaseGetPrimaryKeys: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp); + } + break; + case RemoteMsiSession::MsiDatabaseIsTablePersistent: + { + MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szTable = pReq->fields[1].szValue; + ret = ::MsiDatabaseIsTablePersistent(hDb, szTable); + } + break; + case RemoteMsiSession::MsiDoAction: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szAction = pReq->fields[1].szValue; + ret = ::MsiDoAction(hInstall, szAction); + } + break; + case RemoteMsiSession::MsiEnumComponentCosts: + { + ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiEvaluateCondition: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szCondition = pReq->fields[1].szValue; + ret = ::MsiEvaluateCondition(hInstall, szCondition); + } + break; + case RemoteMsiSession::MsiGetComponentState: + { + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureCost: + { + ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureState: + { + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetFeatureValidStates: + { + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetLanguage: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiGetLanguage(hInstall); + } + break; + case RemoteMsiSession::MsiGetLastErrorRecord: + { + ret = ::MsiGetLastErrorRecord(); + } + break; + case RemoteMsiSession::MsiGetMode: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue; + ret = ::MsiGetMode(hInstall, iRunMode); + } + break; + case RemoteMsiSession::MsiGetSourcePath: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiGetSummaryInformation: + { + ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp); + } + break; + case RemoteMsiSession::MsiGetTargetPath: + { + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend); + } + break; + case RemoteMsiSession::MsiRecordDataSize: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + ret = ::MsiRecordDataSize(hRecord, iField); + } + break; + case RemoteMsiSession::MsiRecordReadStream: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + DWORD cbRead = (DWORD) pReq->fields[2].uiValue; + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2); + if (ret == 0) + { + ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead); + if (ret == 0) + { + pResp->fields[1].vt = VT_STREAM; + pResp->fields[1].szValue = m_pBufSend; + pResp->fields[2].vt = VT_I4; + pResp->fields[2].uiValue = (UINT) cbRead; + } + } + } + break; + case RemoteMsiSession::MsiRecordSetStream: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + UINT iField = pReq->fields[1].uiValue; + const wchar_t* szFilePath = pReq->fields[2].szValue; + ret = ::MsiRecordSetStream(hRecord, iField, szFilePath); + } + break; + case RemoteMsiSession::MsiSequence: + { + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szTable = pReq->fields[1].szValue; + UINT iSequenceMode = pReq->fields[2].uiValue; + ret = ::MsiSequence(hRecord, szTable, iSequenceMode); + } + break; + case RemoteMsiSession::MsiSetComponentState: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szComponent = pReq->fields[1].szValue; + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; + ret = ::MsiSetComponentState(hInstall, szComponent, iState); + } + break; + case RemoteMsiSession::MsiSetFeatureAttributes: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFeature = pReq->fields[1].szValue; + DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue; + ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs); + } + break; + case RemoteMsiSession::MsiSetFeatureState: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFeature = pReq->fields[1].szValue; + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; + ret = ::MsiSetFeatureState(hInstall, szFeature, iState); + } + break; + case RemoteMsiSession::MsiSetInstallLevel: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + int iInstallLevel = pReq->fields[1].iValue; + ret = ::MsiSetInstallLevel(hInstall, iInstallLevel); + } + break; + case RemoteMsiSession::MsiSetMode: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue; + BOOL fState = (BOOL) pReq->fields[2].iValue; + ret = ::MsiSetMode(hInstall, iRunMode, fState); + } + break; + case RemoteMsiSession::MsiSetTargetPath: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + const wchar_t* szFolder = pReq->fields[1].szValue; + const wchar_t* szFolderPath = pReq->fields[2].szValue; + ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath); + } + break; + case RemoteMsiSession::MsiSummaryInfoGetProperty: + { + MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue; + UINT uiProperty = pReq->fields[1].uiValue; + UINT uiDataType; + int iValue; + FILETIME ftValue; + m_pBufSend[0] = L'\0'; + DWORD cchValue = m_cbBufSend; + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); + if (ret == ERROR_MORE_DATA) + { + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue); + if (ret == 0) + { + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); + } + } + if (ret == 0) + { + pResp->fields[1].vt = VT_UI4; + pResp->fields[1].uiValue = uiDataType; + + switch (uiDataType) + { + case VT_I2: + case VT_I4: + pResp->fields[2].vt = VT_I4; + pResp->fields[2].iValue = iValue; + break; + case VT_FILETIME: + pResp->fields[2].vt = VT_UI4; + pResp->fields[2].iValue = ftValue.dwHighDateTime; + pResp->fields[3].vt = VT_UI4; + pResp->fields[3].iValue = ftValue.dwLowDateTime; + break; + case VT_LPSTR: + pResp->fields[2].vt = VT_LPWSTR; + pResp->fields[2].szValue = m_pBufSend; + break; + } + } + } + break; + case RemoteMsiSession::MsiVerifyDiskSpace: + { + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; + ret = ::MsiVerifyDiskSpace(hInstall); + } + break; + + default: + { + ret = ERROR_INVALID_FUNCTION; + } + break; + } + } + + pResp->fields[0].vt = VT_UI4; + pResp->fields[0].uiValue = ret; +} 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define LARGE_BUFFER_THRESHOLD 65536 // bytes +#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts + +/////////////////////////////////////////////////////////////////////////////////////// +// RemoteMsiSession // +////////////////////// +// +// Allows accessing MSI APIs from another process using named pipes. +// +class RemoteMsiSession +{ +public: + + // This enumeration MUST stay in sync with the + // managed equivalent in RemotableNativeMethods.cs! + enum RequestId + { + EndSession = 0, + MsiCloseHandle, + MsiCreateRecord, + MsiDatabaseGetPrimaryKeys, + MsiDatabaseIsTablePersistent, + MsiDatabaseOpenView, + MsiDoAction, + MsiEnumComponentCosts, + MsiEvaluateCondition, + MsiFormatRecord, + MsiGetActiveDatabase, + MsiGetComponentState, + MsiGetFeatureCost, + MsiGetFeatureState, + MsiGetFeatureValidStates, + MsiGetLanguage, + MsiGetLastErrorRecord, + MsiGetMode, + MsiGetProperty, + MsiGetSourcePath, + MsiGetSummaryInformation, + MsiGetTargetPath, + MsiProcessMessage, + MsiRecordClearData, + MsiRecordDataSize, + MsiRecordGetFieldCount, + MsiRecordGetInteger, + MsiRecordGetString, + MsiRecordIsNull, + MsiRecordReadStream, + MsiRecordSetInteger, + MsiRecordSetStream, + MsiRecordSetString, + MsiSequence, + MsiSetComponentState, + MsiSetFeatureAttributes, + MsiSetFeatureState, + MsiSetInstallLevel, + MsiSetMode, + MsiSetProperty, + MsiSetTargetPath, + MsiSummaryInfoGetProperty, + MsiVerifyDiskSpace, + MsiViewExecute, + MsiViewFetch, + MsiViewGetError, + MsiViewGetColumnInfo, + MsiViewModify, + }; + + static const int MAX_REQUEST_FIELDS = 4; + + // Used to pass data back and forth for remote API calls, + // including in & out params & return values. + // Only strings and ints are supported. + struct RequestData + { + struct + { + VARENUM vt; + union { + int iValue; + UINT uiValue; + DWORD cchValue; + LPWSTR szValue; + BYTE* sValue; + DWORD cbValue; + }; + } fields[MAX_REQUEST_FIELDS]; + }; + +public: + + // This value is set from the single data parameter in the EndSession request. + // It saves the exit code of the out-of-proc custom action. + int ExitCode; + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession constructor + // + // Creates a new remote session instance, for use either by the server + // or client process. + // + // szName - Identifies the session instance being remoted. The server and + // the client must use the same name. The name should be unique + // enough to avoid conflicting with other instances on the system. + // + // fServer - True if the calling process is the server process, false if the + // calling process is the client process. + // + RemoteMsiSession(const wchar_t* szName, bool fServer=true) + : m_fServer(fServer), + m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"), + m_szPipeName(NULL), + m_hPipe(NULL), + m_fConnecting(false), + m_fConnected(false), + m_hReceiveThread(NULL), + m_hReceiveStopEvent(NULL), + m_pBufReceive(NULL), + m_cbBufReceive(0), + m_pBufSend(NULL), + m_cbBufSend(0), + ExitCode(ERROR_INSTALL_FAILURE) + { + SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED)); + m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession destructor + // + // Closes any open handles and frees any allocated memory. + // + ~RemoteMsiSession() + { + WaitExitCode(); + if (m_hPipe != NULL) + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + if (m_overlapped.hEvent != NULL) + { + CloseHandle(m_overlapped.hEvent); + m_overlapped.hEvent = NULL; + } + if (m_szPipeName != NULL) + { + delete[] m_szPipeName; + m_szPipeName = NULL; + } + if (m_pBufReceive != NULL) + { + SecureZeroMemory(m_pBufReceive, m_cbBufReceive); + delete[] m_pBufReceive; + m_pBufReceive = NULL; + } + if (m_pBufSend != NULL) + { + SecureZeroMemory(m_pBufSend, m_cbBufSend); + delete[] m_pBufSend; + m_pBufSend = NULL; + } + m_fConnecting = false; + m_fConnected = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::WaitExitCode() + // + // Waits for the server processing thread to complete. + // + void WaitExitCode() + { + if (m_hReceiveThread != NULL) + { + SetEvent(m_hReceiveStopEvent); + WaitForSingleObject(m_hReceiveThread, INFINITE); + CloseHandle(m_hReceiveThread); + m_hReceiveThread = NULL; + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::Connect() + // + // Connects the inter-process communication channel. + // (Currently implemented as a named pipe.) + // + // This method must be called first by the server process, then by the client + // process. The method does not block; the server will asynchronously wait + // for the client process to make the connection. + // + // Returns: 0 on success, Win32 error code on failure. + // + virtual DWORD Connect() + { + const wchar_t* szPipePrefix = L"\\\\.\\pipe\\"; + size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1; + m_szPipeName = new wchar_t[cchPipeNameBuf]; + + if (m_szPipeName == NULL) + { + return ERROR_OUTOFMEMORY; + } + else + { + wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix); + wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName); + + if (m_fServer) + { + return this->ConnectPipeServer(); + } + else + { + return this->ConnectPipeClient(); + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::IsConnected() + // + // Checks if the server process and client process are currently connected. + // + virtual bool IsConnected() const + { + return m_fConnected; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::ProcessRequests() + // + // For use by the service process. Watches for requests in the input buffer and calls + // the callback for each one. + // + // This method does not block; it spawns a separate thread to do the work. + // + // Returns: 0 on success, Win32 error code on failure. + // + virtual DWORD ProcessRequests() + { + return this->StartProcessingReqests(); + } + + ///////////////////////////////////////////////////////////////////////////////////// + // RemoteMsiSession::SendRequest() + // + // For use by the client process. Sends a request to the server and + // synchronously waits on a response, up to the timeout value. + // + // id - ID code of the MSI API call being requested. + // + // pRequest - Pointer to a data structure containing request parameters. + // + // ppResponse - [OUT] Pointer to a location that receives the response parameters. + // + // Returns: 0 on success, Win32 error code on failure. + // Returns WAIT_TIMEOUT if no response was received in time. + // + virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse) + { + if (m_fServer) + { + return ERROR_INVALID_OPERATION; + } + + if (!m_fConnected) + { + *ppResponse = NULL; + return 0; + } + + DWORD dwRet = this->SendRequest(id, pRequest); + if (dwRet != 0) + { + return dwRet; + } + + if (id != EndSession) + { + static RequestData response; + if (ppResponse != NULL) + { + *ppResponse = &response; + } + + return this->ReceiveResponse(id, &response); + } + else + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + m_fConnected = false; + return 0; + } + } + +private: + + // + // Do not allow assignment. + // + RemoteMsiSession& operator=(const RemoteMsiSession&); + + // + // Called only by the server process. + // Create a new thread to handle receiving requests. + // + DWORD StartProcessingReqests() + { + if (!m_fServer || m_hReceiveStopEvent != NULL) + { + return ERROR_INVALID_OPERATION; + } + + DWORD dwRet = 0; + + m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (m_hReceiveStopEvent == NULL) + { + dwRet = GetLastError(); + } + else + { + if (m_hReceiveThread != NULL) + { + CloseHandle(m_hReceiveThread); + } + + m_hReceiveThread = CreateThread(NULL, 0, + RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL); + + if (m_hReceiveThread == NULL) + { + dwRet = GetLastError(); + CloseHandle(m_hReceiveStopEvent); + m_hReceiveStopEvent = NULL; + } + } + + return dwRet; + } + + // + // Called only by the watcher process. + // First verify the connection is complete. Then continually read and parse messages, + // invoke the callback, and send the replies. + // + static DWORD WINAPI ProcessRequestsThreadStatic(void* pv) + { + return reinterpret_cast(pv)->ProcessRequestsThread(); + } + + DWORD ProcessRequestsThread() + { + DWORD dwRet; + + dwRet = CompleteConnection(); + if (dwRet != 0) + { + if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0; + } + + while (m_fConnected) + { + RequestId id; + RequestData req; + dwRet = ReceiveRequest(&id, &req); + if (dwRet != 0) + { + if (dwRet == ERROR_OPERATION_ABORTED || + dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA) + { + dwRet = 0; + } + } + else + { + RequestData resp; + ProcessRequest(id, &req, &resp); + + if (id == EndSession) + { + break; + } + + dwRet = SendResponse(id, &resp); + if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA) + { + dwRet = 0; + } + } + } + + CloseHandle(m_hReceiveStopEvent); + m_hReceiveStopEvent = NULL; + return dwRet; + } + + // + // Called only by the server process's receive thread. + // Read one request into a RequestData object. + // + DWORD ReceiveRequest(RequestId* pId, RequestData* pReq) + { + DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId)); + + if (dwRet == 0) + { + dwRet = this->ReadRequestData(pReq); + } + + return dwRet; + } + + // + // Called by the server process's receive thread or the client's request call + // to read the response. Read data from the pipe, allowing interruption by the + // stop event if on the server. + // + DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead) + { + DWORD dwRet = 0; + DWORD dwTotalBytesRead = 0; + + while (dwRet == 0 && dwTotalBytesRead < cbRead) + { + DWORD dwBytesReadThisTime; + ResetEvent(m_overlapped.hEvent); + if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped)) + { + dwRet = GetLastError(); + if (dwRet == ERROR_IO_PENDING) + { + if (m_fServer) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + } + else + { + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); + } + + if (dwRet == WAIT_OBJECT_0) + { + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE)) + { + dwRet = GetLastError(); + } + } + else if (dwRet == WAIT_FAILED) + { + dwRet = GetLastError(); + } + else + { + dwRet = ERROR_OPERATION_ABORTED; + } + } + } + + dwTotalBytesRead += dwBytesReadThisTime; + } + + if (dwRet != 0) + { + if (m_fServer) + { + CancelIo(m_hPipe); + DisconnectNamedPipe(m_hPipe); + } + else + { + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + m_fConnected = false; + } + + return dwRet; + } + + // + // Called only by the server process. + // Given a request, invoke the MSI API and return the response. + // This is implemented in RemoteMsi.cpp. + // + void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp); + + // + // Called only by the client process. + // Send request data over the pipe. + // + DWORD SendRequest(RequestId id, const RequestData* pRequest) + { + DWORD dwRet = WriteRequestData(id, pRequest); + + if (dwRet != 0) + { + m_fConnected = false; + CloseHandle(m_hPipe); + m_hPipe = NULL; + } + + return dwRet; + } + + // + // Called only by the server process. + // Just send a response over the pipe. + // + DWORD SendResponse(RequestId id, const RequestData* pResp) + { + DWORD dwRet = WriteRequestData(id, pResp); + + if (dwRet != 0) + { + DisconnectNamedPipe(m_hPipe); + m_fConnected = false; + } + + return dwRet; + } + + // + // Called either by the client or server process. + // Writes data to the pipe for a request or response. + // + DWORD WriteRequestData(RequestId id, const RequestData* pReq) + { + DWORD dwRet = 0; + + RequestData req = *pReq; // Make a copy because the const data can't be changed. + + dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId)); + if (dwRet != 0) + { + return dwRet; + } + + BYTE* sValues[MAX_REQUEST_FIELDS] = {0}; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (req.fields[i].vt == VT_LPWSTR) + { + sValues[i] = (BYTE*) req.fields[i].szValue; + req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue); + } + else if (req.fields[i].vt == VT_STREAM) + { + sValues[i] = req.fields[i].sValue; + req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue; + } + } + + dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData)); + if (dwRet != 0) + { + return dwRet; + } + + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (sValues[i] != NULL) + { + DWORD cbValue; + if (req.fields[i].vt == VT_LPWSTR) + { + cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR); + } + else + { + cbValue = req.fields[i].cbValue; + } + + dwRet = this->WritePipe(const_cast (sValues[i]), cbValue); + if (dwRet != 0) + { + break; + } + } + } + + return dwRet; + } + + // + // Called when writing a request or response. Writes data to + // the pipe, allowing interruption by the stop event if on the server. + // + DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite) + { + DWORD dwRet = 0; + DWORD dwTotalBytesWritten = 0; + + while (dwRet == 0 && dwTotalBytesWritten < cbWrite) + { + DWORD dwBytesWrittenThisTime; + ResetEvent(m_overlapped.hEvent); + if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped)) + { + dwRet = GetLastError(); + if (dwRet == ERROR_IO_PENDING) + { + if (m_fServer) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + } + else + { + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); + } + + if (dwRet == WAIT_OBJECT_0) + { + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE)) + { + dwRet = GetLastError(); + } + } + else if (dwRet == WAIT_FAILED) + { + dwRet = GetLastError(); + } + else + { + dwRet = ERROR_OPERATION_ABORTED; + } + } + } + + dwTotalBytesWritten += dwBytesWrittenThisTime; + } + + return dwRet; + } + + // + // Called either by the client or server process. + // Reads data from the pipe for a request or response. + // + DWORD ReadRequestData(RequestData* pReq) + { + DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData)); + + if (dwRet == 0) + { + DWORD cbData = 0; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (pReq->fields[i].vt == VT_LPWSTR) + { + cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); + } + else if (pReq->fields[i].vt == VT_STREAM) + { + cbData += pReq->fields[i].cbValue; + } + } + + if (cbData > 0) + { + if (!CheckRequestDataBuf(cbData)) + { + return ERROR_OUTOFMEMORY; + } + + dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData); + if (dwRet == 0) + { + DWORD dwOffset = 0; + for (int i = 0; i < MAX_REQUEST_FIELDS; i++) + { + if (pReq->fields[i].vt == VT_LPWSTR) + { + LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset); + dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); + pReq->fields[i].szValue = szTemp; + } + else if (pReq->fields[i].vt == VT_STREAM) + { + BYTE* sTemp = m_pBufReceive + dwOffset; + dwOffset += pReq->fields[i].cbValue; + pReq->fields[i].sValue = sTemp; + } + } + } + } + } + + return dwRet; + } + + // + // Called only by the client process. + // Wait for a response on the pipe. If no response is received before the timeout, + // then give up and close the connection. + // + DWORD ReceiveResponse(RequestId id, RequestData* pResp) + { + RequestId responseId; + DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId)); + if (dwRet == 0 && responseId != id) + { + dwRet = ERROR_OPERATION_ABORTED; + } + + if (dwRet == 0) + { + dwRet = this->ReadRequestData(pResp); + } + + return dwRet; + } + + // + // Called only by the server process's receive thread. + // Try to complete and verify an asynchronous connection operation. + // + DWORD CompleteConnection() + { + DWORD dwRet = 0; + if (m_fConnecting) + { + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; + DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); + + if (dwWaitRes == WAIT_OBJECT_0) + { + m_fConnecting = false; + + DWORD dwUnused; + if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + } + } + else if (dwWaitRes == WAIT_FAILED) + { + CancelIo(m_hPipe); + dwRet = GetLastError(); + } + else + { + CancelIo(m_hPipe); + dwRet = ERROR_OPERATION_ABORTED; + } + } + return dwRet; + } + + // + // Called only by the server process. + // Creates a named pipe instance and begins asynchronously waiting + // for a connection from the client process. + // + DWORD ConnectPipeServer() + { + DWORD dwRet = 0; + const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes + m_hPipe = CreateNamedPipe( + m_szPipeName, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, BUFSIZE, BUFSIZE, 0, NULL); + if (m_hPipe == INVALID_HANDLE_VALUE) + { + m_hPipe = NULL; + dwRet = GetLastError(); + } + else if (ConnectNamedPipe(m_hPipe, &m_overlapped)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + + if (dwRet == ERROR_PIPE_BUSY) + { + // All pipe instances are busy, so wait for a maximum of 20 seconds + dwRet = 0; + if (WaitNamedPipe(m_szPipeName, 20000)) + { + m_fConnected = true; + } + else + { + dwRet = GetLastError(); + } + } + + if (dwRet == ERROR_IO_PENDING) + { + dwRet = 0; + m_fConnecting = true; + } + } + return dwRet; + } + + // + // Called only by the client process. + // Attemps to open a connection to an existing named pipe instance + // which should have already been created by the server process. + // + DWORD ConnectPipeClient() + { + DWORD dwRet = 0; + m_hPipe = CreateFile( + m_szPipeName, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (m_hPipe != INVALID_HANDLE_VALUE) + { + m_fConnected = true; + } + else + { + m_hPipe = NULL; + dwRet = GetLastError(); + } + return dwRet; + } + + // + // Ensures that the request buffer is large enough to hold a request, + // reallocating the buffer if necessary. + // It will also reduce the buffer size if the previous allocation was very large. + // + BOOL CheckRequestDataBuf(DWORD cbBuf) + { + if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive)) + { + if (m_pBufReceive != NULL) + { + SecureZeroMemory(m_pBufReceive, m_cbBufReceive); + delete[] m_pBufReceive; + } + m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf); + m_pBufReceive = new BYTE[m_cbBufReceive]; + if (m_pBufReceive == NULL) + { + m_cbBufReceive = 0; + } + } + return m_pBufReceive != NULL; + } + +private: + + // Name of this instance. + const wchar_t* m_szName; + + // "\\.\pipe\name" + wchar_t* m_szPipeName; + + // Handle to the pipe instance. + HANDLE m_hPipe; + + // Handle to the thread that receives requests. + HANDLE m_hReceiveThread; + + // Handle to the event used to signal the receive thread to exit. + HANDLE m_hReceiveStopEvent; + + // All pipe I/O is done in overlapped mode to avoid unintentional blocking. + OVERLAPPED m_overlapped; + + // Dynamically-resized buffer for receiving requests. + BYTE* m_pBufReceive; + + // Current size of the receive request buffer. + DWORD m_cbBufReceive; + + // Dynamically-resized buffer for sending requests. + wchar_t* m_pBufSend; + + // Current size of the send request buffer. + DWORD m_cbBufSend; + + // True if this is the server process, false if this is the client process. + const bool m_fServer; + + // True if an asynchronous connection operation is currently in progress. + bool m_fConnecting; + + // True if the pipe is currently connected. + bool m_fConnected; +}; 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#include "EntryPoints.h" +#include "SfxUtil.h" + +#define MANAGED_CAs_OUT_OF_PROC 1 + +HMODULE g_hModule; +bool g_fRunningOutOfProc = false; + +RemoteMsiSession* g_pRemote = NULL; + +// Prototypes for local functions. +// See the function definitions for comments. + +bool InvokeManagedCustomAction(MSIHANDLE hSession, + _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); + +/// +/// Entry-point for the CA DLL when re-launched as a separate process; +/// connects the comm channel for remote MSI APIs, then invokes the +/// managed custom action entry-point. +/// +/// +/// Do not change the parameters or calling-convention: RUNDLL32 +/// requires this exact signature. +/// +extern "C" +void __stdcall InvokeManagedCustomActionOutOfProc( + __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) +{ + UNREFERENCED_PARAMETER(hwnd); + UNREFERENCED_PARAMETER(hinst); + UNREFERENCED_PARAMETER(nCmdShow); + + g_fRunningOutOfProc = true; + + const wchar_t* szSessionName = szCmdLine; + MSIHANDLE hSession; + const wchar_t* szEntryPoint; + + int i; + for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; + hSession = _wtoi(szCmdLine + i); + + for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; + szEntryPoint = szCmdLine + i; + + g_pRemote = new RemoteMsiSession(szSessionName, false); + g_pRemote->Connect(); + + int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); + + RemoteMsiSession::RequestData requestData; + SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); + requestData.fields[0].vt = VT_I4; + requestData.fields[0].iValue = ret; + g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); + delete g_pRemote; +} + +/// +/// Re-launch this CA DLL as a separate process, and setup a comm channel +/// for remote MSI API calls back to this process. +/// +int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) +{ + wchar_t szSessionName[100] = {0}; + swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); + + RemoteMsiSession remote(szSessionName, true); + + DWORD ret = remote.Connect(); + if (ret != 0) + { + Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); + return ERROR_INSTALL_FAILURE; + } + + ret = remote.ProcessRequests(); + if (ret != 0) + { + Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); + return ERROR_INSTALL_FAILURE; + } + + wchar_t szModule[MAX_PATH] = {0}; + GetModuleFileName(g_hModule, szModule, MAX_PATH); + + const wchar_t* rundll32 = L"rundll32.exe"; + wchar_t szRunDll32Path[MAX_PATH] = {0}; + GetSystemDirectory(szRunDll32Path, MAX_PATH); + wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); + wcscat_s(szRunDll32Path, MAX_PATH, rundll32); + + const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; + wchar_t szCommandLine[1024] = {0}; + swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", + rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); + + STARTUPINFO si; + SecureZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + PROCESS_INFORMATION pi; + SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, + 0, NULL, NULL, &si, &pi)) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + + DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); + if (dwWait != WAIT_OBJECT_0) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to wait for CA process. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + + DWORD dwExitCode; + BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); + if (!bRet) + { + DWORD err = GetLastError(); + Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); + return ERROR_INSTALL_FAILURE; + } + else if (dwExitCode != 0) + { + Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); + return ERROR_INSTALL_FAILURE; + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + remote.WaitExitCode(); + return remote.ExitCode; +} + +/// +/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to +/// call MSI APIs remotely. +/// +void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) +{ + if (g_fRunningOutOfProc) + { + g_pRemote->SendRequest(id, pRequest, ppResponse); + } + else + { + *ppResponse = NULL; + } +} + +/// +/// Invokes a managed custom action from native code by +/// extracting the package to a temporary working directory +/// then hosting the CLR and locating and calling the entrypoint. +/// +/// Handle to the installation session. +/// Passed to custom action entrypoints by the installer engine. +/// Directory containing the CA binaries +/// and the CustomAction.config file defining the entrypoints. +/// This may be NULL, in which case the current module must have +/// a concatenated cabinet containing those files, which will be +/// extracted to a temporary directory. +/// Name of the CA entrypoint to be invoked. +/// This must be either an explicit "AssemblyName!Namespace.Class.Method" +/// string, or a simple name that maps to a full entrypoint definition +/// in CustomAction.config. +/// The value returned by the managed custom action method, +/// or ERROR_INSTALL_FAILURE if the CA could not be invoked. +int InvokeCustomAction(MSIHANDLE hSession, + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) +{ +#ifdef MANAGED_CAs_OUT_OF_PROC + if (!g_fRunningOutOfProc && szWorkingDir == NULL) + { + return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); + } +#endif + + wchar_t szTempDir[MAX_PATH]; + bool fDeleteTemp = false; + if (szWorkingDir == NULL) + { + if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) + { + return ERROR_INSTALL_FAILURE; + } + szWorkingDir = szTempDir; + fDeleteTemp = true; + } + + wchar_t szConfigFilePath[MAX_PATH + 20]; + StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); + + const wchar_t* szConfigFile = szConfigFilePath; + if (!::PathFileExists(szConfigFilePath)) + { + szConfigFile = NULL; + } + + wchar_t szWIAssembly[MAX_PATH + 50]; + StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); + + int iResult = ERROR_INSTALL_FAILURE; + ICorRuntimeHost* pHost; + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) + { + _AppDomain* pAppDomain; + if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, + szConfigFile, &pAppDomain)) + { + if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) + { + iResult = ERROR_INSTALL_FAILURE; + } + HRESULT hr = pHost->UnloadDomain(pAppDomain); + if (FAILED(hr)) + { + Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); + } + pAppDomain->Release(); + } + + pHost->Stop(); + pHost->Release(); + } + + if (fDeleteTemp) + { + DeleteDirectory(szTempDir); + } + return iResult; +} + +/// +/// Called by the system when the DLL is loaded. +/// Saves the module handle for later use. +/// +BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) +{ + UNREFERENCED_PARAMETER(pReserved); + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +/// +/// Loads and invokes the managed portion of the proxy. +/// +/// Handle to the installer session, +/// used for logging errors and to be passed on to the custom action. +/// AppDomain which has its application +/// base set to the CA working directory. +/// Name of the CA entrypoint to be invoked. +/// This must be either an explicit "AssemblyName!Namespace.Class.Method" +/// string, or a simple name that maps to a full entrypoint definition +/// in CustomAction.config. +/// Return value of the invoked custom +/// action method. +/// True if the managed proxy was invoked successfully, +/// false if there was some error. Note the custom action itself may +/// return an error via piResult while this method still returns true +/// since the invocation was successful. +bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szEntryPoint, int* piResult) +{ + VARIANT vResult; + ::VariantInit(&vResult); + + const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; + const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; + const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); + + _MethodInfo* pCAInvokeMethod; + if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, + szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) + { + return false; + } + + HRESULT hr; + VARIANT vNull; + vNull.vt = VT_EMPTY; + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); + VARIANT vSessionHandle; + vSessionHandle.vt = VT_I4; + vSessionHandle.intVal = hSession; + LONG index = 0; + hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); + if (FAILED(hr)) goto LExit; + VARIANT vEntryPoint; + vEntryPoint.vt = VT_BSTR; + vEntryPoint.bstrVal = SysAllocString(szEntryPoint); + if (vEntryPoint.bstrVal == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + index = 1; + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); + if (FAILED(hr)) goto LExit; + VARIANT vRemotingFunctionPtr; +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant + if (f64bit) +#pragma warning(pop) + { + vRemotingFunctionPtr.vt = VT_I8; + vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); + } + else + { + vRemotingFunctionPtr.vt = VT_I4; +#pragma warning(push) +#pragma warning(disable:4302) // truncation +#pragma warning(disable:4311) // pointer truncation + vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); +#pragma warning(pop) + } + index = 2; + hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); + if (FAILED(hr)) goto LExit; + + hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); + +LExit: + SafeArrayDestroy(saArgs); + pCAInvokeMethod->Release(); + + if (FAILED(hr)) + { + Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); + return false; + } + + *piResult = vResult.intVal; + return true; +} + 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define VER_DLL +#define VER_LANG_NEUTRAL +#define VER_ORIGINAL_FILENAME "SfxCA.dll" +#define VER_INTERNAL_NAME "SfxCA" +#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action" + +// Additional resources here + 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 @@ + + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + + {55D5BA28-D427-4F53-80C2-FE9EF23C1553} + DynamicLibrary + Unicode + false + EntryPoints.def + + + + + + + msi.lib;cabinet.lib;shlwapi.lib + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {81c92f68-18c2-4cd4-a588-5c3616860dd9} + + + {6cdc30ee-e14d-4679-b92e-3e080535e53b} + + + {1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d} + + + + + Resource Files + + + + + Resource Files + + + + \ 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#include "SfxUtil.h" + +/// +/// Writes a formatted message to the MSI log. +/// Does out-of-proc MSI calls if necessary. +/// +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) +{ + const int LOG_BUFSIZE = 4096; + wchar_t szBuf[LOG_BUFSIZE]; + va_list args; + va_start(args, szMessage); + StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); + + if (!g_fRunningOutOfProc || NULL == g_pRemote) + { + MSIHANDLE hRec = MsiCreateRecord(1); + MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); + MsiRecordSetString(hRec, 1, szBuf); + MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); + MsiCloseHandle(hRec); + } + else + { + // Logging is the only remote-MSI operation done from unmanaged code. + // It's not very convenient here because part of the infrastructure + // for remote MSI APIs is on the managed side. + + RemoteMsiSession::RequestData req; + RemoteMsiSession::RequestData* pResp = NULL; + SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); + + req.fields[0].vt = VT_UI4; + req.fields[0].uiValue = 1; + g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); + MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_UI4; + req.fields[1].uiValue = 0; + req.fields[2].vt = VT_LPWSTR; + req.fields[2].szValue = L"SFXCA: [1]"; + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_UI4; + req.fields[1].uiValue = 1; + req.fields[2].vt = VT_LPWSTR; + req.fields[2].szValue = szBuf; + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hSession; + req.fields[1].vt = VT_I4; + req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; + req.fields[2].vt = VT_I4; + req.fields[2].iValue = (int) hRec; + g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); + + req.fields[0].vt = VT_I4; + req.fields[0].iValue = (int) hRec; + req.fields[1].vt = VT_EMPTY; + req.fields[2].vt = VT_EMPTY; + g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); + } +} + +/// +/// Deletes a directory, including all files and subdirectories. +/// +/// Path to the directory to delete, +/// not including a trailing backslash. +/// True if the directory was successfully deleted, or false +/// if the deletion failed (most likely because some files were locked). +/// +bool DeleteDirectory(const wchar_t* szDir) +{ + size_t cchDir = wcslen(szDir); + size_t cchPathBuf = cchDir + 3 + MAX_PATH; + wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); + if (szPath == NULL) return false; + StringCchCopy(szPath, cchPathBuf, szDir); + StringCchCat(szPath, cchPathBuf, L"\\*"); + WIN32_FIND_DATA fd; + HANDLE hSearch = FindFirstFile(szPath, &fd); + while (hSearch != INVALID_HANDLE_VALUE) + { + StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) + { + DeleteDirectory(szPath); + } + } + else + { + DeleteFile(szPath); + } + if (!FindNextFile(hSearch, &fd)) + { + FindClose(hSearch); + hSearch = INVALID_HANDLE_VALUE; + } + } + return RemoveDirectory(szDir) != 0; +} + +bool DirectoryExists(const wchar_t* szDir) +{ + if (szDir != NULL) + { + DWORD dwAttrs = GetFileAttributes(szDir); + if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + return true; + } + } + return false; +} + +/// +/// Extracts a cabinet that is concatenated to a module +/// to a new temporary directory. +/// +/// Handle to the installer session, +/// used just for logging. +/// Module that has the concatenated cabinet. +/// Buffer for returning the path of the +/// created temp directory. +/// Size in characters of the buffer. +/// True if the files were extracted, or false if the +/// buffer was too small or the directory could not be created +/// or the extraction failed for some other reason. +__success(return != false) +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) +{ + wchar_t szModule[MAX_PATH]; + DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); + if (cchCopied == 0) + { + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); + return false; + } + else if (cchCopied == MAX_PATH - 1) + { + Log(hSession, L"Failed to get module path -- path is too long."); + return false; + } + + if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) + { + Log(hSession, L"Temp directory buffer is NULL or too small."); + return false; + } + StringCchCopy(szTempDir, cchTempDirBuf, szModule); + StringCchCat(szTempDir, cchTempDirBuf, L"-"); + + DWORD cchTempDir = (DWORD) wcslen(szTempDir); + for (int i = 0; DirectoryExists(szTempDir); i++) + { + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); + } + + if (!CreateDirectory(szTempDir, NULL)) + { + cchCopied = GetTempPath(cchTempDirBuf, szTempDir); + if (cchCopied == 0 || cchCopied >= cchTempDirBuf) + { + Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError()); + return false; + } + + wchar_t* szModuleName = wcsrchr(szModule, L'\\'); + if (szModuleName == NULL) szModuleName = szModule; + else szModuleName = szModuleName + 1; + StringCchCat(szTempDir, cchTempDirBuf, szModuleName); + StringCchCat(szTempDir, cchTempDirBuf, L"-"); + + cchTempDir = (DWORD) wcslen(szTempDir); + for (int i = 0; DirectoryExists(szTempDir); i++) + { + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); + } + + if (!CreateDirectory(szTempDir, NULL)) + { + Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError()); + return false; + } + } + + Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); + int err = ExtractCabinet(szModule, szTempDir); + if (err != 0) + { + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); + DeleteDirectory(szTempDir); + return false; + } + return true; +} + 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "RemoteMsiSession.h" + +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); + +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); + +bool DeleteDirectory(const wchar_t* szDir); + +__success(return != false) +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); + +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); + +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, + const wchar_t* szName, const wchar_t* szAppBase, + const wchar_t* szConfigFile, _AppDomain** ppAppDomain); + +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, + const wchar_t* szAssembly, const wchar_t* szClass, + const wchar_t* szMethod, _MethodInfo** ppCAMethod); + +extern HMODULE g_hModule; +extern bool g_fRunningOutOfProc; + +extern RemoteMsiSession* g_pRemote; + + 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" \ 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 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#import raw_interfaces_only rename("ReportEvent", "CorReportEvent") +using 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 @@ + + + + + + + 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 @@ + + + + + + net472 + false + The WiX Toolset Managed CustomAction Framework. + 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. + + $(OutputPath) + + + + + + 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 @@ + + + + $id$ + $version$ + $title$ + $description$ + $authors$ + wix-white-bg.png + MS-RL + false + $copyright$ + $projectUrl$ + + + + + + + + + + + + + + + + + + + diff --git a/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets new file mode 100644 index 00000000..127bb29d --- /dev/null +++ b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets @@ -0,0 +1,121 @@ + + + + + + + + + true + + $(TargetName).CA$(TargetExt) + + $(MSBuildThisFileDirectory)..\tools\ + + $(MakeSfxCAPath)WixToolset.Dtf.MakeSfxCA.exe + $(MakeSfxCAPath)arm64\SfxCA.dll + $(MakeSfxCAPath)x64\SfxCA.dll + $(MakeSfxCAPath)x86\SfxCA.dll + + + + + + + + + + + + + + + + + + + @(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + \ 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 @@ - - - - - - - netcoreapp3.1 - false - WiX Toolset Dtf MSBuild integration - $(MSBuildThisFileName).nuspec - $(OutputPath)publish\WixToolset.Dtf.MSBuild\ - Id=$(MSBuildThisFileName);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description) - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - - - $(GenerateNuspecDependsOn);SetNuspecVersion - - - - - - - $(NuspecProperties);Version=$(Version);ProjectFolder=$(MSBuildThisFileDirectory) - - - 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 @@ - - - - $id$ - $version$ - $authors$ - $authors$ - MS-RL - false - $description$ - $copyright$ - - - - - - - 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 @@ - - - - - - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\tools\wix.ca.targets')) - - diff --git a/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets b/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets deleted file mode 100644 index 4578c2d8..00000000 --- a/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - true - - $(TargetName).CA$(TargetExt) - - $(MSBuildThisFileDirectory) - $(WixSdkPath)x86\ - $(WixSdkPath)x64\ - - $(WixSdkPath)MakeSfxCA.exe - $(WixSdkX64Path)SfxCA.dll - $(WixSdkX86Path)SfxCA.dll - - - - - - - - - - - - - - - - - - - @(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.MakeSfxCA +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Security; + using System.Text; + using System.Reflection; + using WixToolset.Dtf.Compression; + using WixToolset.Dtf.Compression.Cab; + using WixToolset.Dtf.Resources; + using ResourceCollection = WixToolset.Dtf.Resources.ResourceCollection; + + /// + /// Command-line tool for building self-extracting custom action packages. + /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's + /// entry-points and file version to look like the CA module. + /// + public static class MakeSfxCA + { + private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; + + private static TextWriter log; + + /// + /// Prints usage text for the tool. + /// + /// Console text writer. + public static void Usage(TextWriter w) + { + w.WriteLine("WiX Toolset custom action packager version {0}", Assembly.GetExecutingAssembly().GetName().Version); + w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); + w.WriteLine(); + w.WriteLine("Usage: WixToolset.Dtf.MakeSfxCA SfxCA.dll [support files ...]"); + w.WriteLine(); + w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); + w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); + w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); + } + + /// + /// Runs the MakeSfxCA command-line tool. + /// + /// Command-line arguments. + /// 0 on success, nonzero on failure. + public static int Main(string[] args) + { + if (args.Length < 3) + { + Usage(Console.Out); + return 1; + } + + var output = args[0]; + var sfxDll = args[1]; + var inputs = new string[args.Length - 2]; + Array.Copy(args, 2, inputs, 0, inputs.Length); + + try + { + Build(output, sfxDll, inputs, Console.Out); + return 0; + } + catch (ArgumentException ex) + { + Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); + return 1; + } + catch (FileNotFoundException ex) + { + Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); + return 1; + } + catch (Exception ex) + { + Console.Error.WriteLine("Error: Unexpected error: " + ex); + return 1; + } + } + + /// + /// Packages up all the inputs to the output location. + /// + /// Various exceptions are thrown + /// if things go wrong. + public static void Build(string output, string sfxDll, IList inputs, TextWriter log) + { + MakeSfxCA.log = log; + + if (String.IsNullOrEmpty(output)) + { + throw new ArgumentNullException("output"); + } + + if (String.IsNullOrEmpty(sfxDll)) + { + throw new ArgumentNullException("sfxDll"); + } + + if (inputs == null || inputs.Count == 0) + { + throw new ArgumentNullException("inputs"); + } + + if (!File.Exists(sfxDll)) + { + throw new FileNotFoundException(sfxDll); + } + + var customActionAssembly = inputs[0]; + if (!File.Exists(customActionAssembly)) + { + throw new FileNotFoundException(customActionAssembly); + } + + inputs = MakeSfxCA.SplitList(inputs); + + var inputsMap = MakeSfxCA.GetPackFileMap(inputs); + + var foundWIAssembly = false; + foreach (var input in inputsMap.Keys) + { + if (String.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, + StringComparison.OrdinalIgnoreCase) == 0) + { + foundWIAssembly = true; + } + } + + if (!foundWIAssembly) + { + throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + + " must be included in the list of support files. " + + "If using the MSBuild targets, make sure the assembly reference " + + "has the Private (Copy Local) flag set."); + } + + MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); + + var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); + var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); + + if (entryPoints.Count == 0 && uiClass == null) + { + throw new ArgumentException( + "No CA or UI entry points found in module: " + customActionAssembly); + } + else if (entryPoints.Count > 0 && uiClass != null) + { + throw new NotSupportedException( + "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); + } + + var dir = Path.GetDirectoryName(output); + if (dir.Length > 0 && !Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + using (Stream outputStream = File.Create(output)) + { + MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); + } + + MakeSfxCA.CopyVersionResource(customActionAssembly, output); + + MakeSfxCA.PackInputFiles(output, inputsMap); + + log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); + } + + /// + /// Splits any list items delimited by semicolons into separate items. + /// + /// Read-only input list. + /// New list with resulting split items. + private static IList SplitList(IList list) + { + var newList = new List(list.Count); + + foreach (var item in list) + { + if (!String.IsNullOrEmpty(item)) + { + foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + newList.Add(splitItem); + } + } + } + + return newList; + } + + /// + /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. + /// + /// List of input files which include non-GAC dependent assemblies. + /// Directory to auto-locate additional dependent assemblies. + /// + /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them + /// to the list of input files if found. + /// + private static void ResolveDependentAssemblies(IDictionary inputFiles, string inputDir) + { + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) + { + AssemblyName resolveName = new AssemblyName(args.Name); + Assembly assembly = null; + + // First, try to find the assembly in the list of input files. + foreach (var inputFile in inputFiles.Values) + { + var inputName = Path.GetFileNameWithoutExtension(inputFile); + var inputExtension = Path.GetExtension(inputFile); + if (String.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && + (String.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || + String.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) + { + assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); + + if (assembly != null) + { + break; + } + } + } + + // Second, try to find the assembly in the input directory. + if (assembly == null && inputDir != null) + { + string assemblyPath = null; + if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) + { + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; + } + else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) + { + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; + } + + if (assemblyPath != null) + { + assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); + + if (assembly != null) + { + // Add this detected dependency to the list of files to be packed. + inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); + } + } + } + + // Third, try to load the assembly from the GAC. + if (assembly == null) + { + try + { + assembly = Assembly.ReflectionOnlyLoad(args.Name); + } + catch (FileNotFoundException) + { + } + } + + if (assembly != null) + { + if (String.Equals(assembly.GetName().ToString(), resolveName.ToString())) + { + log.WriteLine(" Loaded dependent assembly: " + assembly.Location); + return assembly; + } + + log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); + log.WriteLine(" Loaded assembly : " + assembly.GetName()); + log.WriteLine(" Reference assembly: " + resolveName); + } + else + { + log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); + } + + return null; + }; + } + + /// + /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. + /// + /// Path of the assembly file to laod. + /// Loaded assembly, or null if the load failed. + private static Assembly TryLoadDependentAssembly(string assemblyPath) + { + Assembly assembly = null; + try + { + assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); + } + catch (IOException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + catch (BadImageFormatException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + catch (SecurityException ex) + { + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); + } + + return assembly; + } + + /// + /// Searches the types in the input assembly for a type that implements IEmbeddedUI. + /// + /// + /// + private static string FindEmbeddedUIClass(string module) + { + log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); + + string uiClass = null; + + var assembly = Assembly.ReflectionOnlyLoadFrom(module); + + foreach (var type in assembly.GetExportedTypes()) + { + if (!type.IsAbstract) + { + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") + { + if (uiClass == null) + { + uiClass = assembly.GetName().Name + "!" + type.FullName; + } + else + { + throw new ArgumentException("Multiple IEmbeddedUI implementations found."); + } + } + } + } + } + + return uiClass; + } + + /// + /// Reflects on an input CA module to locate custom action entry-points. + /// + /// Assembly module with CA entry-points. + /// Mapping from entry-point names to assembly!class.method paths. + private static IDictionary FindEntryPoints(string module) + { + log.WriteLine("Searching for custom action entry points " + + "in {0}", Path.GetFileName(module)); + + var entryPoints = new Dictionary(); + + var assembly = Assembly.ReflectionOnlyLoadFrom(module); + + foreach (var type in assembly.GetExportedTypes()) + { + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + var entryPointName = MakeSfxCA.GetEntryPoint(method); + if (entryPointName != null) + { + var entryPointPath = String.Format( + "{0}!{1}.{2}", + Path.GetFileNameWithoutExtension(module), + type.FullName, + method.Name); + entryPoints.Add(entryPointName, entryPointPath); + + log.WriteLine(" {0}={1}", entryPointName, entryPointPath); + } + } + } + + return entryPoints; + } + + /// + /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. + /// + /// A public static method. + /// Entrypoint name for the method as specified by the custom action attribute or just the method name, + /// or null if the method is not a custom action method. + private static string GetEntryPoint(MethodInfo method) + { + IList attributes; + try + { + attributes = CustomAttributeData.GetCustomAttributes(method); + } + catch (FileLoadException) + { + // Already logged load failures in the assembly-resolve-handler. + return null; + } + + foreach (CustomAttributeData attribute in attributes) + { + if (attribute.ToString().StartsWith( + "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", + StringComparison.Ordinal)) + { + string entryPointName = null; + foreach (var argument in attribute.ConstructorArguments) + { + // The entry point name is the first positional argument, if specified. + entryPointName = (string) argument.Value; + break; + } + + if (String.IsNullOrEmpty(entryPointName)) + { + entryPointName = method.Name; + } + + return entryPointName; + } + } + + return null; + } + + /// + /// Counts the number of template entrypoints in SfxCA.dll. + /// + /// + /// Depending on the requirements, SfxCA.dll might be built with + /// more entrypoints than the default. + /// + private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) + { + for (var count = 0; ; count++) + { + var templateName = String.Format(entryPointFormat, count); + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); + + var nameOffset = FindBytes(fileBytes, templateAsciiBytes); + if (nameOffset < 0) + { + return count; + } + } + } + + /// + /// Writes a modified version of SfxCA.dll to the output stream, + /// with the template entry-points mapped to the CA entry-points. + /// + /// + /// To avoid having to recompile SfxCA.dll for every different set of CAs, + /// this method looks for a preset number of template entry-points in the + /// binary file and overwrites their entrypoint name and string data with + /// CA-specific values. + /// + private static void WriteEntryModule( + string sfxDll, Stream outputStream, IDictionary entryPoints, string uiClass) + { + log.WriteLine("Modifying SfxCA.dll stub"); + + byte[] fileBytes; + using (var readStream = File.OpenRead(sfxDll)) + { + fileBytes = new byte[(int) readStream.Length]; + readStream.Read(fileBytes, 0, fileBytes.Length); + } + + const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; + const int MAX_ENTRYPOINT_NAME = 72; + const int MAX_ENTRYPOINT_PATH = 160; + //var emptyBytes = new byte[0]; + + var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); + + if (slotCount == 0) + { + throw new ArgumentException("Invalid SfxCA.dll file."); + } + + if (entryPoints.Count > slotCount) + { + throw new ArgumentException(String.Format( + "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + + "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", + entryPoints.Count, slotCount)); + } + + var slotSort = new string[slotCount]; + for (var i = 0; i < slotCount - entryPoints.Count; i++) + { + slotSort[i] = String.Empty; + } + + entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); + Array.Sort(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); + + for (var i = 0; ; i++) + { + var templateName = String.Format(ENTRYPOINT_FORMAT, i); + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); + var templateUniBytes = Encoding.Unicode.GetBytes(templateName); + + var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); + if (nameOffset < 0) + { + break; + } + + var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); + if (pathOffset < 0) + { + break; + } + + var entryPointName = slotSort[i]; + var entryPointPath = entryPointName.Length > 0 ? + entryPoints[entryPointName] : String.Empty; + + if (entryPointName.Length > MAX_ENTRYPOINT_NAME) + { + throw new ArgumentException(String.Format( + "Entry point name exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_NAME, + entryPointName)); + } + + if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) + { + throw new ArgumentException(String.Format( + "Entry point path exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_PATH, + entryPointPath)); + } + + var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); + var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); + + MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); + MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); + } + + if (entryPoints.Count == 0 && uiClass != null) + { + // Remove the zzz prefix from exported EmbeddedUI entry-points. + foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) + { + var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); + + var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); + if (exportOffset < 0) + { + throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); + } + + var replaceNameBytes = Encoding.ASCII.GetBytes(export); + MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); + } + + if (uiClass.Length > MAX_ENTRYPOINT_PATH) + { + throw new ArgumentException(String.Format( + "UI class full name exceeds limit of {0} characters: {1}", + MAX_ENTRYPOINT_PATH, + uiClass)); + } + + var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); + var replaceBytes = Encoding.Unicode.GetBytes(uiClass); + + // Fill in the embedded UI implementor class so the proxy knows which one to load. + var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); + if (replaceOffset >= 0) + { + MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); + } + } + + outputStream.Write(fileBytes, 0, fileBytes.Length); + } + + /// + /// Searches for a sub-array of bytes within a larger array of bytes. + /// + private static int FindBytes(byte[] source, byte[] find) + { + for (var i = 0; i < source.Length; i++) + { + int j; + for (j = 0; j < find.Length; j++) + { + if (source[i + j] != find[j]) + { + break; + } + } + + if (j == find.Length) + { + return i; + } + } + + return -1; + } + + /// + /// Replaces a range of bytes with new bytes, padding any extra part + /// of the range with zeroes. + /// + private static void ReplaceBytes( + byte[] source, int offset, int length, byte[] replace) + { + for (var i = 0; i < length; i++) + { + if (i < replace.Length) + { + source[offset + i] = replace[i]; + } + else + { + source[offset + i] = 0; + } + } + } + + /// + /// Print the name of one file as it is being packed into the cab. + /// + private static void PackProgress(object source, ArchiveProgressEventArgs e) + { + if (e.ProgressType == ArchiveProgressType.StartFile && log != null) + { + log.WriteLine(" {0}", e.CurrentFileName); + } + } + + /// + /// Gets a mapping from filenames as they will be in the cab to filenames + /// as they are currently on disk. + /// + /// + /// By default, all files will be placed in the root of the cab. But inputs may + /// optionally include an alternate inside-cab file path before an equals sign. + /// + private static IDictionary GetPackFileMap(IList inputs) + { + var fileMap = new Dictionary(); + foreach (var inputFile in inputs) + { + if (inputFile.IndexOf('=') > 0) + { + var parse = inputFile.Split('='); + if (!fileMap.ContainsKey(parse[0])) + { + fileMap.Add(parse[0], parse[1]); + } + } + else + { + var fileName = Path.GetFileName(inputFile); + if (!fileMap.ContainsKey(fileName)) + { + fileMap.Add(fileName, inputFile); + } + } + } + return fileMap; + } + + /// + /// Packs the input files into a cab that is appended to the + /// output SfxCA.dll. + /// + private static void PackInputFiles(string outputFile, IDictionary fileMap) + { + log.WriteLine("Packaging files"); + + var cabInfo = new CabInfo(outputFile); + cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); + } + + /// + /// Copies the version resource information from the CA module to + /// the CA package. This gives the package the file version and + /// description of the CA module, instead of the version and + /// description of SfxCA.dll. + /// + private static void CopyVersionResource(string sourceFile, string destFile) + { + log.WriteLine("Copying file version info from {0} to {1}", + sourceFile, destFile); + + var rc = new ResourceCollection(); + rc.Find(sourceFile, ResourceType.Version); + rc.Load(sourceFile); + rc.Save(destFile); + } + } +} 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 @@ + + + + WiX Toolset Compiler + + + + + true + + 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 @@ + + + + + + Exe + net472 + false + embedded + app.config + MakeSfxCA.exe.manifest + + + + + + + + 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 @@ + + + + + + + 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 /// /// To use this class: /// Create a new ResourceCollection - /// Locate resources for the collection by calling one of the methods + /// Locate resources for the collection by calling one of the methods /// Load data of one or more s from a file by calling the method of the /// Resource class, or load them all at once (more efficient) with the method of the ResourceCollection. /// Read and/or edit data of the individual Resource objects using the methods on that class. @@ -28,7 +28,7 @@ namespace WixToolset.Dtf.Resources /// public class ResourceCollection : ICollection { - private List resources; + private readonly List resources; /// /// Creates a new, empty ResourceCollection. @@ -48,17 +48,17 @@ namespace WixToolset.Dtf.Resources { this.Clear(); - IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); + var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); if (module == IntPtr.Zero) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err)); } try { if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err)); } } @@ -79,12 +79,12 @@ namespace WixToolset.Dtf.Resources { this.Clear(); - IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); + var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); try { if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err)); } } @@ -106,12 +106,12 @@ namespace WixToolset.Dtf.Resources { this.Clear(); - IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); + var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); try { if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err)); } } @@ -123,9 +123,9 @@ namespace WixToolset.Dtf.Resources private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param) { - if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(EnumResNames), IntPtr.Zero)) + if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err)); } return true; @@ -133,9 +133,9 @@ namespace WixToolset.Dtf.Resources private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param) { - if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(EnumResLangs), IntPtr.Zero)) + if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err)); } return true; @@ -179,10 +179,10 @@ namespace WixToolset.Dtf.Resources /// The file from which resources are loaded. public void Load(string file) { - IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); + var module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); try { - foreach (Resource res in this) + foreach (var res in this) { res.Load(module); } @@ -199,17 +199,17 @@ namespace WixToolset.Dtf.Resources /// The file to which resources are saved. public void Save(string file) { - IntPtr updateHandle = IntPtr.Zero; + var updateHandle = IntPtr.Zero; try { updateHandle = NativeMethods.BeginUpdateResource(file, false); - foreach (Resource res in this) + foreach (var res in this) { res.Save(updateHandle); } if (!NativeMethods.EndUpdateResource(updateHandle, false)) { - int err = Marshal.GetLastWin32Error(); + var err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err)); } updateHandle = IntPtr.Zero; diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs deleted file mode 100644 index 981ecc69..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs +++ /dev/null @@ -1,1165 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Text; - using System.Threading; - using System.Collections.Generic; - using System.Runtime.Serialization; - using System.Runtime.Serialization.Formatters.Binary; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.Compression; - using WixToolset.Dtf.Compression.Cab; - - [TestClass] - public class CabTest - { - public CabTest() - { - } - - [TestInitialize] - public void Initialize() - { - } - - [TestCleanup] - public void Cleanup() - { - } - - [TestMethod] - public void CabinetMultithread() - { - this.multithreadExceptions = new List(); - - const int threadCount = 10; - IList threads = new List(threadCount); - - for (int i = 0; i < threadCount; i++) - { - Thread thread = new Thread(new ThreadStart(this.CabinetMultithreadWorker)); - thread.Name = "CabinetMultithreadWorker_" + i; - threads.Add(thread); - } - - foreach (Thread thread in threads) - { - thread.Start(); - } - - foreach (Thread thread in threads) - { - thread.Join(); - } - - foreach (Exception ex in this.multithreadExceptions) - { - Console.WriteLine(); - Console.WriteLine(ex); - } - Assert.AreEqual(0, this.multithreadExceptions.Count); - } - - private IList multithreadExceptions; - - private void CabinetMultithreadWorker() - { - try - { - string threadName = Thread.CurrentThread.Name; - int threadNumber = Int32.Parse(threadName.Substring(threadName.IndexOf('_') + 1)); - this.RunCabinetPackUnpack(100, 10240 + threadNumber, 0, 0, CompressionLevel.Normal); - } - catch (Exception ex) - { - this.multithreadExceptions.Add(ex); - } - } - - [TestMethod] - public void CabinetFileCounts() - { - this.RunCabinetPackUnpack(0, 10, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(100, 10, 0, 0, CompressionLevel.Normal); - } - - [TestMethod] - [Ignore] // Takes ~5 minutes and 66000 is over the 65535 limit anyway. - public void CabinetExtremeFileCounts() - { - this.RunCabinetPackUnpack(66000, 10); - } - - [TestMethod] - public void CabinetFileSizes() - { - this.RunCabinetPackUnpack(1, 0, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 1, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 2, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 3, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 4, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 5, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 6, 0, 0, CompressionLevel.Normal); - // Skip file sizes 7-9: see "buggy" file sizes test below. - this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 11, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 12, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 100 * 1024, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 10 * 1024 * 1024, 0, 0, CompressionLevel.Normal); - } - - [TestMethod] - public void CabinetBuggyFileSizes() - { - // Windows' cabinet.dll has a known bug (#55001 in Windows OS Bugs) - // LZX compression causes an AV with file sizes of 7, 8, or 9 bytes. - try - { - this.RunCabinetPackUnpack(1, 7, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 8, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 9, 0, 0, CompressionLevel.Normal); - } - catch (AccessViolationException) - { - Assert.Fail("Known 7,8,9 file size bug detected in Windows' cabinet.dll."); - } - } - - [Timeout(36000000), TestMethod] - [Ignore] // Takes too long to run regularly. - public void CabinetExtremeFileSizes() - { - this.RunCabinetPackUnpack(10, 512L * 1024 * 1024); // 5GB - //this.RunCabinetPackUnpack(1, 5L * 1024 * 1024 * 1024); // 5GB - } - - [TestMethod] - public void CabinetFolders() - { - this.RunCabinetPackUnpack(0, 10, 1, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(1, 10, 1, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(100, 10, 1, 0, CompressionLevel.Normal); - - IList fileInfo; - fileInfo = this.RunCabinetPackUnpack(7, 100 * 1024, 250 * 1024, 0, CompressionLevel.None); - Assert.AreEqual(2, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, - "Testing whether cabinet has the correct # of folders."); - - fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 250 * 1024, 0, CompressionLevel.None); - Assert.AreEqual(3, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, - "Testing whether cabinet has the correct # of folders."); - - fileInfo = this.RunCabinetPackUnpack(2, 100 * 1024, 40 * 1024, 0, CompressionLevel.None); - Assert.AreEqual(1, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, - "Testing whether cabinet has the correct # of folders."); - } - - [TestMethod] - public void CabinetArchiveCounts() - { - IList fileInfo; - fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 0, 400 * 1024, CompressionLevel.None); - Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, - "Testing whether archive spans the correct # of cab files."); - - fileInfo = this.RunCabinetPackUnpack(2, 90 * 1024, 0, 40 * 1024, CompressionLevel.None); - Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, - "Testing whether archive spans the correct # of cab files."); - } - - [TestMethod] - public void CabinetProgress() - { - CompressionTestUtil.ExpectedProgress = new List(new int[][] { - // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs - new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartArchive, 7, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 7, 15, 3, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartArchive, 13, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 13, 15, 6, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartArchive, 14, 15, 7, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 }, - // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs - new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 3, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 3, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 6, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 6, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 }, - }); - - try - { - this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024, CompressionLevel.None); - } - finally - { - CompressionTestUtil.ExpectedProgress = null; - } - } - - [TestMethod] - public void CabArchiveSizeParam() - { - Console.WriteLine("Testing various values for the maxArchiveSize parameter."); - this.RunCabinetPackUnpack(5, 1024, 0, Int64.MinValue); - this.RunCabinetPackUnpack(5, 1024, 0, -1); - this.RunCabinetPackUnpack(5, 10, 0, 2); - this.RunCabinetPackUnpack(5, 100, 0, 256); - this.RunCabinetPackUnpack(5, 24000, 0, 32768); - this.RunCabinetPackUnpack(5, 1024, 0, Int64.MaxValue); - } - - [TestMethod] - public void CabFolderSizeParam() - { - Console.WriteLine("Testing various values for the maxFolderSize parameter."); - this.RunCabinetPackUnpack(5, 10, Int64.MinValue, 0); - this.RunCabinetPackUnpack(5, 10, -1, 0); - this.RunCabinetPackUnpack(5, 10, 2, 0); - this.RunCabinetPackUnpack(5, 10, 16, 0); - this.RunCabinetPackUnpack(5, 10, 100, 0); - this.RunCabinetPackUnpack(5, 10, Int64.MaxValue, 0); - } - - [TestMethod] - public void CabCompLevelParam() - { - Console.WriteLine("Testing various values for the compressionLevel parameter."); - this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.None); - this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Min); - this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Normal); - this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Max); - this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.None - 1)); - this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1)); - this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MinValue); - this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MaxValue); - } - - [TestMethod] - public void CabEngineNullParams() - { - string[] testFiles = new string[] { "test.txt" }; - ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.cab", null, null); - - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.CompressionLevel = CompressionLevel.None; - - CompressionTestUtil.TestCompressionEngineNullParams( - cabEngine, streamContext, testFiles); - } - } - - [TestMethod] - public void CabBadPackStreamContexts() - { - string[] testFiles = new string[] { "test.txt" }; - CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000); - - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.CompressionLevel = CompressionLevel.None; - - CompressionTestUtil.TestBadPackStreamContexts(cabEngine, "test.cab", testFiles); - } - } - - [TestMethod] - public void CabEngineNoTempFileTest() - { - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("testnotemp.txt", 0, txtSize); - - ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("testnotemp.cab", null, null); - - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.UseTempFiles = false; - cabEngine.Pack(streamContext, new string[] { "testnotemp.txt" }); - } - - new CabInfo("testnotemp.cab").UnpackFile("testnotemp.txt", "testnotemp2.txt"); - Assert.AreEqual(txtSize, new FileInfo("testnotemp2.txt").Length); - } - - [TestMethod] - public void CabExtractorIsCabinet() - { - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); - new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null); - using (CabEngine cabEngine = new CabEngine()) - { - bool isCab; - using (Stream fileStream = File.OpenRead("test.txt")) - { - isCab = cabEngine.IsArchive(fileStream); - } - Assert.IsFalse(isCab); - using (Stream cabStream = File.OpenRead("test.cab")) - { - isCab = cabEngine.IsArchive(cabStream); - } - Assert.IsTrue(isCab); - using (Stream cabStream = File.OpenRead("test.cab")) - { - using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) - { - fileStream.Seek(0, SeekOrigin.End); - byte[] buf = new byte[1024]; - int count; - while ((count = cabStream.Read(buf, 0, buf.Length)) > 0) - { - fileStream.Write(buf, 0, count); - } - fileStream.Seek(0, SeekOrigin.Begin); - isCab = cabEngine.IsArchive(fileStream); - } - } - Assert.IsFalse(isCab); - using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) - { - fileStream.Write(new byte[] { (byte) 'M', (byte) 'S', (byte) 'C', (byte) 'F' }, 0, 4); - fileStream.Seek(0, SeekOrigin.Begin); - isCab = cabEngine.IsArchive(fileStream); - } - Assert.IsFalse(isCab); - } - } - - [TestMethod] - public void CabExtractorFindOffset() - { - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); - new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null); - using (CabEngine cabEngine = new CabEngine()) - { - long offset; - using (Stream fileStream = File.OpenRead("test.txt")) - { - offset = cabEngine.FindArchiveOffset(fileStream); - } - Assert.AreEqual(-1, offset); - using (Stream cabStream = File.OpenRead("test.cab")) - { - using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) - { - fileStream.Seek(0, SeekOrigin.End); - byte[] buf = new byte[1024]; - int count; - while ((count = cabStream.Read(buf, 0, buf.Length)) > 0) - { - fileStream.Write(buf, 0, count); - } - fileStream.Seek(0, SeekOrigin.Begin); - offset = cabEngine.FindArchiveOffset(fileStream); - } - } - Assert.AreEqual(txtSize, offset); - } - } - - [TestMethod] - public void CabExtractorGetFiles() - { - IList fileInfo; - CabInfo cabInfo = new CabInfo("testgetfiles.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("testgetfiles0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("testgetfiles1.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "testgetfiles0.txt", "testgetfiles1.txt" }, null); - using (CabEngine cabEngine = new CabEngine()) - { - IList files; - using (Stream cabStream = File.OpenRead("testgetfiles.cab")) - { - files = cabEngine.GetFiles(cabStream); - } - Assert.IsNotNull(files); - Assert.AreEqual(2, files.Count); - Assert.AreEqual("testgetfiles0.txt", files[0]); - Assert.AreEqual("testgetfiles1.txt", files[1]); - - using (Stream cabStream = File.OpenRead("testgetfiles.cab")) - { - files = cabEngine.GetFiles(new ArchiveFileStreamContext("testgetfiles.cab"), null); - } - Assert.IsNotNull(files); - Assert.AreEqual(2, files.Count); - Assert.AreEqual("testgetfiles0.txt", files[0]); - Assert.AreEqual("testgetfiles1.txt", files[1]); - - using (Stream cabStream = File.OpenRead("testgetfiles.cab")) - { - fileInfo = cabEngine.GetFileInfo(cabStream); - } - Assert.IsNotNull(fileInfo); - Assert.AreEqual(2, fileInfo.Count); - Assert.AreEqual("testgetfiles0.txt", fileInfo[0].Name); - Assert.AreEqual("testgetfiles1.txt", fileInfo[1].Name); - using (Stream cabStream = File.OpenRead("testgetfiles.cab")) - { - fileInfo = cabEngine.GetFileInfo(new ArchiveFileStreamContext("testgetfiles.cab"), null); - } - Assert.IsNotNull(fileInfo); - Assert.AreEqual(2, fileInfo.Count); - Assert.AreEqual("testgetfiles0.txt", fileInfo[0].Name); - Assert.AreEqual("testgetfiles1.txt", fileInfo[1].Name); - } - - fileInfo = this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024); - Assert.IsNotNull(fileInfo); - Assert.AreEqual(15, fileInfo.Count); - for (int i = 0; i < fileInfo.Count; i++) - { - Assert.IsNull(fileInfo[i].Archive); - Assert.AreEqual(TEST_FILENAME_PREFIX + i + ".txt", fileInfo[i].Name); - Assert.IsTrue(DateTime.Now - fileInfo[i].LastWriteTime < new TimeSpan(0, 1, 0)); - } - } - - [TestMethod] - public void CabExtractorExtract() - { - int txtSize = 40960; - CabInfo cabInfo = new CabInfo("test.cab"); - CompressionTestUtil.GenerateRandomFile("test0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test1.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "test0.txt", "test1.txt" }, null); - using (CabEngine cabEngine = new CabEngine()) - { - using (Stream cabStream = File.OpenRead("test.cab")) - { - using (Stream exStream = cabEngine.Unpack(cabStream, "test0.txt")) - { - string str = new StreamReader(exStream).ReadToEnd(); - string expected = new StreamReader("test0.txt").ReadToEnd(); - Assert.AreEqual(expected, str); - } - cabStream.Seek(0, SeekOrigin.Begin); - using (Stream exStream = cabEngine.Unpack(cabStream, "test1.txt")) - { - string str = new StreamReader(exStream).ReadToEnd(); - string expected = new StreamReader("test1.txt").ReadToEnd(); - Assert.AreEqual(expected, str); - } - } - using (Stream txtStream = File.OpenRead("test0.txt")) - { - Exception caughtEx = null; - try - { - cabEngine.Unpack(txtStream, "test0.txt"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(CabException)); - Assert.AreEqual(2, ((CabException) caughtEx).Error); - Assert.AreEqual(0, ((CabException) caughtEx).ErrorCode); - Assert.AreEqual("Cabinet file does not have the correct format.", caughtEx.Message); - } - } - } - - [TestMethod] - public void CabBadUnpackStreamContexts() - { - int txtSize = 40960; - CabInfo cabInfo = new CabInfo("test2.cab"); - CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null); - - using (CabEngine cabEngine = new CabEngine()) - { - CompressionTestUtil.TestBadUnpackStreamContexts(cabEngine, "test2.cab"); - } - } - - [TestMethod] - public void CabinetExtractUpdate() - { - int fileCount = 5, fileSize = 2048; - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = "€" + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - - CabInfo cabInfo = new CabInfo("testupdate.cab"); - cabInfo.Pack(dirA); - cabInfo.Unpack(dirB); - - DateTime originalTime = File.GetLastWriteTime(Path.Combine(dirA, "€1.txt")); - DateTime pastTime = originalTime - new TimeSpan(0, 5, 0); - DateTime futureTime = originalTime + new TimeSpan(0, 5, 0); - - using (CabEngine cabEngine = new CabEngine()) - { - string cabName = "testupdate.cab"; - ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(cabName, dirB, null); - streamContext.ExtractOnlyNewerFiles = true; - - Assert.AreEqual(true, streamContext.ExtractOnlyNewerFiles); - Assert.IsNotNull(streamContext.ArchiveFiles); - Assert.AreEqual(1, streamContext.ArchiveFiles.Count); - Assert.AreEqual(cabName, streamContext.ArchiveFiles[0]); - Assert.AreEqual(dirB, streamContext.Directory); - - File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), futureTime); - cabEngine.Unpack(streamContext, null); - Assert.IsTrue(File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime > new TimeSpan(0, 4, 55)); - - File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), pastTime); - File.SetLastWriteTime(Path.Combine(dirB, "€2.txt"), pastTime); - File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.ReadOnly); - File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.Hidden); - File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.System); - - cabEngine.Unpack(streamContext, null); - Assert.IsTrue((File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime).Duration() < new TimeSpan(0, 0, 5)); - - // Just test the rest of the streamContext properties here. - IDictionary testMap = new Dictionary(); - streamContext = new ArchiveFileStreamContext(cabName, dirB, testMap); - Assert.AreSame(testMap, streamContext.Files); - - Assert.IsFalse(streamContext.EnableOffsetOpen); - streamContext.EnableOffsetOpen = true; - Assert.IsTrue(streamContext.EnableOffsetOpen); - streamContext = new ArchiveFileStreamContext(cabName, ".", testMap); - Assert.AreEqual(".", streamContext.Directory); - string[] testArchiveFiles = new string[] { cabName }; - streamContext = new ArchiveFileStreamContext(testArchiveFiles, ".", testMap); - Assert.AreSame(testArchiveFiles, streamContext.ArchiveFiles); - } - } - - [TestMethod] - public void CabinetOffset() - { - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("base.txt", 1, 2 * txtSize + 4); - - ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("base.txt", null, null); - streamContext.EnableOffsetOpen = true; - - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.Pack(streamContext, new string[] { "test.txt" }); - } - - Assert.IsTrue(new FileInfo("base.txt").Length > 2 * txtSize + 4); - - string saveText; - using (Stream txtStream = File.OpenRead("test.txt")) - { - saveText = new StreamReader(txtStream).ReadToEnd(); - } - File.Delete("test.txt"); - - using (CabEngine cex = new CabEngine()) - { - cex.Unpack(streamContext, null); - } - string testText; - using (Stream txtStream = File.OpenRead("test.txt")) - { - testText = new StreamReader(txtStream).ReadToEnd(); - } - Assert.AreEqual(saveText, testText); - } - - [TestMethod] - public void CabinetUtfPaths() - { - string[] files = new string[] - { - "어그리먼트送信ポート1ßà_Agreement.txt", - "콘토소ßà_MyProfile.txt", - "파트너1ßà_PartnerProfile.txt", - }; - - string dirA = "utf8-A"; - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = "utf8-B"; - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - int txtSize = 1024; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[0]), 0, txtSize); - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[1]), 1, txtSize); - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[2]), 2, txtSize); - - ArchiveFileStreamContext streamContextA = new ArchiveFileStreamContext("utf8.cab", dirA, null); - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.Pack(streamContextA, files); - } - - ArchiveFileStreamContext streamContextB = new ArchiveFileStreamContext("utf8.cab", dirB, null); - using (CabEngine cex = new CabEngine()) - { - cex.Unpack(streamContextB, null); - } - - bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - } - - [TestMethod] - //[Ignore] // Requires clean environment. - public void CabInfoProperties() - { - Exception caughtEx; - CabInfo cabInfo = new CabInfo("test.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); - - Assert.AreEqual(new FileInfo("test.cab").Directory.FullName, cabInfo.Directory.FullName, "CabInfo.FullName"); - Assert.AreEqual(new FileInfo("test.cab").DirectoryName, cabInfo.DirectoryName, "CabInfo.DirectoryName"); - Assert.AreEqual(new FileInfo("test.cab").Length, cabInfo.Length, "CabInfo.Length"); - Assert.AreEqual("test.cab", cabInfo.Name, "CabInfo.Name"); - Assert.AreEqual(new FileInfo("test.cab").FullName, cabInfo.ToString(), "CabInfo.ToString()"); - cabInfo.CopyTo("test3.cab"); - caughtEx = null; - try - { - cabInfo.CopyTo("test3.cab"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(IOException), "CabInfo.CopyTo() caught exception: " + caughtEx); - cabInfo.CopyTo("test3.cab", true); - cabInfo.MoveTo("test4.cab"); - Assert.AreEqual("test4.cab", cabInfo.Name); - Assert.IsTrue(cabInfo.Exists, "CabInfo.Exists()"); - Assert.IsTrue(cabInfo.IsValid(), "CabInfo.IsValid"); - cabInfo.Delete(); - Assert.IsFalse(cabInfo.Exists, "!CabInfo.Exists()"); - } - - [TestMethod] - //[Ignore] // Requires clean environment. - public void CabInfoNullParams() - { - int fileCount = 10, fileSize = 1024; - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = "cabinfo-" + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - - CabInfo cabInfo = new CabInfo("testnull.cab"); - - CompressionTestUtil.TestArchiveInfoNullParams(cabInfo, dirA, dirB, files); - } - - [TestMethod] - public void CabInfoGetFiles() - { - IList fileInfo; - CabInfo cabInfo = new CabInfo("test.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt" }, null); - - fileInfo = cabInfo.GetFiles(); - Assert.IsNotNull(fileInfo); - Assert.AreEqual(2, fileInfo.Count); - Assert.AreEqual("testinfo0.txt", fileInfo[0].Name); - Assert.AreEqual("testinfo1.txt", fileInfo[1].Name); - - fileInfo = cabInfo.GetFiles("*.txt"); - Assert.IsNotNull(fileInfo); - Assert.AreEqual(2, fileInfo.Count); - Assert.AreEqual("testinfo0.txt", fileInfo[0].Name); - Assert.AreEqual("testinfo1.txt", fileInfo[1].Name); - - fileInfo = cabInfo.GetFiles("testinfo1.txt"); - Assert.IsNotNull(fileInfo); - Assert.AreEqual(1, fileInfo.Count); - Assert.AreEqual("testinfo1.txt", fileInfo[0].Name); - } - - [TestMethod] - public void CabInfoCompressExtract() - { - int fileCount = 10, fileSize = 1024; - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - Directory.CreateDirectory(Path.Combine(dirA, "sub")); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = "€" + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - CompressionTestUtil.GenerateRandomFile(Path.Combine(Path.Combine(dirA, "sub"), "€-.txt"), fileCount + 1, fileSize); - - CabInfo cabInfo = new CabInfo("test.cab"); - cabInfo.Pack(dirA); - cabInfo.Unpack(dirB); - bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsFalse(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - cabInfo.Pack(dirA, true, CompressionLevel.Normal, null); - cabInfo.Unpack(dirB); - directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - Directory.Delete(dirB, true); - Directory.Delete(Path.Combine(dirA, "sub"), true); - Directory.CreateDirectory(dirB); - cabInfo.Delete(); - - cabInfo.PackFiles(dirA, files, null); - cabInfo.UnpackFiles(files, dirB, null); - directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - cabInfo.Delete(); - - IDictionary testMap = new Dictionary(files.Length); - for (int iFile = 0; iFile < fileCount; iFile++) - { - testMap[files[iFile] + ".key"] = files[iFile]; - } - cabInfo.PackFileSet(dirA, testMap); - cabInfo.UnpackFileSet(testMap, dirB); - directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - testMap.Remove(files[1] + ".key"); - cabInfo.UnpackFileSet(testMap, dirB); - directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsFalse(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - cabInfo.Delete(); - - cabInfo.PackFiles(dirA, files, null); - cabInfo.UnpackFile("€2.txt", Path.Combine(dirB, "test.txt")); - Assert.IsTrue(File.Exists(Path.Combine(dirB, "test.txt"))); - Assert.AreEqual(1, Directory.GetFiles(dirB).Length); - } - - [TestMethod] - //[Ignore] // Requires clean environment. - public void CabFileInfoProperties() - { - CabInfo cabInfo = new CabInfo("test.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); - File.SetAttributes("test01.txt", FileAttributes.ReadOnly | FileAttributes.Archive); - DateTime testTime = File.GetLastWriteTime("test01.txt"); - cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); - File.SetAttributes("test01.txt", FileAttributes.Archive); - - CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); - Assert.AreEqual(cabInfo.FullName, cfi.CabinetName); - Assert.AreEqual(0, ((CabFileInfo) cfi).CabinetFolderNumber); - Assert.AreEqual(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.FullName); - cfi = new CabFileInfo(cabInfo, "test01.txt"); - Assert.IsTrue(cfi.Exists); - cfi = new CabFileInfo(cabInfo, "test01.txt"); - Assert.AreEqual(txtSize, cfi.Length); - cfi = new CabFileInfo(cabInfo, "test00.txt"); - Assert.AreEqual(FileAttributes.Archive, cfi.Attributes); - cfi = new CabFileInfo(cabInfo, "test01.txt"); - Assert.AreEqual(FileAttributes.ReadOnly | FileAttributes.Archive, cfi.Attributes); - cfi = new CabFileInfo(cabInfo, "test01.txt"); - Assert.IsTrue((testTime - cfi.LastWriteTime).Duration() < new TimeSpan(0, 0, 5)); - Assert.AreEqual(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.ToString()); - cfi.CopyTo("testcopy.txt"); - Assert.IsTrue(File.Exists("testCopy.txt")); - Assert.AreEqual(cfi.Length, new FileInfo("testCopy.txt").Length); - - Exception caughtEx = null; - try - { - cfi.CopyTo("testcopy.txt", false); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(IOException)); - } - - [TestMethod] - public void CabFileInfoOpenText() - { - CabInfo cabInfo = new CabInfo("test.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); - - string expectedText = File.ReadAllText("test01.txt"); - - cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); - - CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); - using (StreamReader cabFileReader = cfi.OpenText()) - { - string text = cabFileReader.ReadToEnd(); - Assert.AreEqual(expectedText, text); - - // Check the assumption that the cab can't be deleted while a stream is open. - Exception caughtEx = null; - try - { - File.Delete(cabInfo.FullName); - } - catch (Exception ex) - { - caughtEx = ex; - } - - Assert.IsInstanceOfType(caughtEx, typeof(IOException)); - } - - // Ensure all streams are closed after disposing of the StreamReader returned by OpenText. - File.Delete(cabInfo.FullName); - } - - [TestMethod] - public void CabFileInfoNullParams() - { - Exception caughtEx; - CabInfo cabInfo = new CabInfo("test.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); - CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); - - caughtEx = null; - try - { - new CabFileInfo(null, "test00.txt"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - caughtEx = null; - try - { - new CabFileInfo(cabInfo, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - caughtEx = null; - try - { - cfi.CopyTo(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - } - - [TestMethod] - public void CabInfoSerialization() - { - CabInfo cabInfo = new CabInfo("testser.cab"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("testser00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("testser01.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "testser00.txt", "testser01.txt" }, null); - ArchiveFileInfo cfi = cabInfo.GetFiles()[1]; - - MemoryStream memStream = new MemoryStream(); - - BinaryFormatter formatter = new BinaryFormatter(); - - memStream.Seek(0, SeekOrigin.Begin); - formatter.Serialize(memStream, cabInfo); - memStream.Seek(0, SeekOrigin.Begin); - CabInfo cabInfo2 = (CabInfo) formatter.Deserialize(memStream); - Assert.AreEqual(cabInfo.FullName, cabInfo2.FullName); - - memStream.Seek(0, SeekOrigin.Begin); - formatter.Serialize(memStream, cfi); - memStream.Seek(0, SeekOrigin.Begin); - CabFileInfo cfi2 = (CabFileInfo) formatter.Deserialize(memStream); - Assert.AreEqual(cfi.FullName, cfi2.FullName); - Assert.AreEqual(cfi.Length, cfi2.Length); - - CabException cabEx = new CabException(); - memStream.Seek(0, SeekOrigin.Begin); - formatter.Serialize(memStream, cabEx); - memStream.Seek(0, SeekOrigin.Begin); - formatter.Deserialize(memStream); - - cabEx = new CabException("Test exception.", null); - Assert.AreEqual("Test exception.", cabEx.Message); - } - - [TestMethod] - public void CabFileStreamContextNullParams() - { - ArchiveFileStreamContext streamContext = null; - Exception caughtEx = null; - try - { - streamContext = new ArchiveFileStreamContext(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing null to constructor."); - caughtEx = null; - try - { - streamContext = new ArchiveFileStreamContext(new string[] { }, "testDir", new Dictionary()); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing 0-length array to constructor."); - caughtEx = null; - try - { - streamContext = new ArchiveFileStreamContext(new string[] { "test.cab" }, null, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx); - } - - [TestMethod] - public void CabinetTruncateOnCreate() - { - CabInfo cabInfo = new CabInfo("testtruncate.cab"); - int txtSize = 20240; - CompressionTestUtil.GenerateRandomFile("testtruncate0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("testtruncate1.txt", 1, txtSize); - cabInfo.PackFiles(null, new string[] { "testtruncate0.txt", "testtruncate1.txt" }, null); - - long size1 = cabInfo.Length; - - txtSize /= 5; - CompressionTestUtil.GenerateRandomFile("testtruncate2.txt", 2, txtSize); - CompressionTestUtil.GenerateRandomFile("testtruncate3.txt", 3, txtSize); - cabInfo.PackFiles(null, new string[] { "testtruncate2.txt", "testtruncate3.txt" }, null); - - // The newly created cab file should be smaller than before. - Assert.AreNotEqual(size1, cabInfo.Length, "Checking that cabinet file got truncated when creating a smaller cab in-place."); - } - - [TestMethod] - public void CabTruncatedArchive() - { - CabInfo cabInfo = new CabInfo("test-t.cab"); - CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, 5); - CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, 5); - cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null); - - CompressionTestUtil.TestTruncatedArchive(cabInfo, typeof(CabException)); - } - private const string TEST_FILENAME_PREFIX = "\x20AC"; - - private IList RunCabinetPackUnpack(int fileCount, long fileSize) - { - return RunCabinetPackUnpack(fileCount, fileSize, 0, 0); - } - private IList RunCabinetPackUnpack(int fileCount, long fileSize, - long maxFolderSize, long maxArchiveSize) - { - return this.RunCabinetPackUnpack(fileCount, fileSize, maxFolderSize, maxArchiveSize, CompressionLevel.Normal); - } - private IList RunCabinetPackUnpack(int fileCount, long fileSize, - long maxFolderSize, long maxArchiveSize, CompressionLevel compLevel) - { - Console.WriteLine("Creating cabinet with {0} files of size {1}", - fileCount, fileSize); - Console.WriteLine("MaxFolderSize={0}, MaxArchiveSize={1}, CompressionLevel={2}", - maxFolderSize, maxArchiveSize, compLevel); - - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - - string[] archiveNames = new string[100]; - for (int i = 0; i < archiveNames.Length; i++) - { - archiveNames[i] = String.Format("{0}-{1}{2}{3}.cab", fileCount, fileSize, - (i == 0 ? "" : "-"), (i == 0 ? "" : i.ToString())); - } - - string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize); - CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile); - - IList fileInfo; - using (CabEngine cabEngine = new CabEngine()) - { - cabEngine.CompressionLevel = compLevel; - - File.AppendAllText(progressTextFile, - "\r\n\r\n====================================================\r\nCREATE\r\n\r\n"); - cabEngine.Progress += testUtil.PrintArchiveProgress; - - OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null); - if (maxFolderSize == 1) - { - streamContext.OptionHandler = - delegate(string optionName, object[] parameters) - { - if (optionName == "nextFolder") return true; - return null; - }; - } - else if (maxFolderSize > 1) - { - streamContext.OptionHandler = - delegate(string optionName, object[] parameters) - { - if (optionName == "maxFolderSize") return maxFolderSize; - return null; - }; - } - cabEngine.Pack(streamContext, files, maxArchiveSize); - - IList createdArchiveNames = new List(archiveNames.Length); - for (int i = 0; i < archiveNames.Length; i++) - { - if (File.Exists(archiveNames[i])) - { - createdArchiveNames.Add(archiveNames[i]); - } - else - { - break; - } - } - - Console.WriteLine("Listing cabinet with {0} files of size {1}", - fileCount, fileSize); - File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n"); - fileInfo = cabEngine.GetFileInfo( - new ArchiveFileStreamContext(createdArchiveNames, null, null), null); - - Assert.AreEqual(fileCount, fileInfo.Count); - if (fileCount > 0) - { - int folders = ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber + 1; - if (maxFolderSize == 1) - { - Assert.AreEqual(fileCount, folders); - } - } - - Console.WriteLine("Extracting cabinet with {0} files of size {1}", - fileCount, fileSize); - File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n"); - cabEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null); - } - - bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether cabinet output directory matches input directory."); - - return fileInfo; - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj b/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj deleted file mode 100644 index e751d405..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - - {4544158C-2D63-4146-85FF-62169280144E} - Library - WixToolsetTests.Dtf.Cab - WixToolsetTests.Dtf.Cab - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - false - false - v4.7.2 - - - - - - - - - - - - - - - - {45D81DAB-0559-4836-8106-CE9987FD4AB5} - WixToolset.Dtf.Compression - - - {E56C0ED3-FA2F-4CA9-A1C0-2E796BB0BF80} - WixToolset.Dtf.Compression.Cab - - - {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} - WixToolsetTests.Dtf.Compression - - - - - - diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj deleted file mode 100644 index 6ee102ae..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - - {328799BB-7B03-4B28-8180-4132211FD07D} - Library - WixToolsetTests.Dtf - WixToolsetTests.Dtf.Zip - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - false - false - v4.7.2 - - - - - - - - - - - - - - {45D81DAB-0559-4836-8106-CE9987FD4AB5} - WixToolset.Dtf.Compression - - - {E4C60A57-8AFE-4FF3-9058-ACAC6A069533} - WixToolset.Dtf.Compression.Zip - - - {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} - WixToolsetTests.Dtf.Compression - - - - - - diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs b/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs deleted file mode 100644 index b264ad5b..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Text; - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.Compression; - using WixToolset.Dtf.Compression.Zip; - - [TestClass] - public class ZipTest - { - public ZipTest() - { - } - - [TestInitialize] - public void Initialize() - { - } - - [TestCleanup] - public void Cleanup() - { - } - - [TestMethod] - public void ZipFileCounts() - { - this.RunZipPackUnpack(0, 10, 0); - this.RunZipPackUnpack(0, 100000, 0); - this.RunZipPackUnpack(1, 10, 0); - this.RunZipPackUnpack(100, 10, 0); - } - - [TestMethod] - [Ignore] // Takes too long to run regularly. - public void ZipExtremeFileCounts() - { - this.RunZipPackUnpack(66000, 10, 0); - } - - [TestMethod] - public void ZipFileSizes() - { - this.RunZipPackUnpack(1, 0, 0); - for (int n = 1; n <= 33; n++) - { - this.RunZipPackUnpack(1, n, 0); - } - this.RunZipPackUnpack(1, 100 * 1024, 0); - this.RunZipPackUnpack(1, 10 * 1024 * 1024, 0); - } - - [Timeout(36000000), TestMethod] - [Ignore] // Takes too long to run regularly. - public void ZipExtremeFileSizes() - { - //this.RunZipPackUnpack(10, 512L * 1024 * 1024, 0); // 5GB - this.RunZipPackUnpack(1, 5L * 1024 * 1024 * 1024, 0, CompressionLevel.None); // 5GB - } - - [TestMethod] - public void ZipArchiveCounts() - { - IList fileInfo; - fileInfo = this.RunZipPackUnpack(10, 100 * 1024, 400 * 1024, CompressionLevel.None); - Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, - "Testing whether archive spans the correct # of zip files."); - - fileInfo = this.RunZipPackUnpack(2, 90 * 1024, 40 * 1024, CompressionLevel.None); - Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, - "Testing whether archive spans the correct # of zip files."); - } - - [TestMethod] - public void ZipProgress() - { - CompressionTestUtil.ExpectedProgress = new List(new int[][] { - // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives - new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 1 }, - new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 2 }, - new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 }, - // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives - new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 3 }, - new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 3 }, - new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 }, - new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 }, - }); - CompressionTestUtil.ExpectedProgress = null; - - try - { - this.RunZipPackUnpack(15, 20 * 1024, 130 * 1024, CompressionLevel.None); - } - finally - { - CompressionTestUtil.ExpectedProgress = null; - } - } - - [TestMethod] - //[Ignore] // Requires clean environment. - public void ZipArchiveSizes() - { - Console.WriteLine("Testing various values for the maxArchiveSize parameter."); - this.RunZipPackUnpack(5, 1024, Int64.MinValue); - this.RunZipPackUnpack(5, 1024, -1); - this.RunZipPackUnpack(2, 10, 0); - - this.RunZipPackUnpack(1, 10, 1); - this.RunZipPackUnpack(2, 10, 2); - this.RunZipPackUnpack(2, 10, 3); - this.RunZipPackUnpack(2, 10, 4); - this.RunZipPackUnpack(2, 10, 5); - this.RunZipPackUnpack(2, 10, 6); - this.RunZipPackUnpack(2, 10, 7); - this.RunZipPackUnpack(5, 10, 8); - this.RunZipPackUnpack(5, 10, 9); - this.RunZipPackUnpack(5, 10, 10); - this.RunZipPackUnpack(5, 10, 11); - this.RunZipPackUnpack(5, 10, 12); - - this.RunZipPackUnpack(5, 101, 255); - this.RunZipPackUnpack(5, 102, 256); - this.RunZipPackUnpack(5, 103, 257); - this.RunZipPackUnpack(5, 24000, 32768); - this.RunZipPackUnpack(5, 1024, Int64.MaxValue); - } - - [TestMethod] - public void ZipCompLevelParam() - { - Console.WriteLine("Testing various values for the compressionLevel parameter."); - this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.None); - this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Min); - this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Normal); - this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Max); - this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.None - 1)); - this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1)); - this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MinValue); - this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MaxValue); - } - - [TestMethod] - public void ZipInfoGetFiles() - { - IList fileInfos; - ZipInfo zipInfo = new ZipInfo("testgetfiles.zip"); - - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize); - CompressionTestUtil.GenerateRandomFile("testinfo2.ini", 2, txtSize); - zipInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt", "testinfo2.ini" }, null); - - fileInfos = zipInfo.GetFiles(); - Assert.IsNotNull(fileInfos); - Assert.AreEqual(3, fileInfos.Count); - Assert.AreEqual("testinfo0.txt", fileInfos[0].Name); - Assert.AreEqual("testinfo1.txt", fileInfos[1].Name); - Assert.AreEqual("testinfo2.ini", fileInfos[2].Name); - - fileInfos = zipInfo.GetFiles("*.txt"); - Assert.IsNotNull(fileInfos); - Assert.AreEqual(2, fileInfos.Count); - Assert.AreEqual("testinfo0.txt", fileInfos[0].Name); - Assert.AreEqual("testinfo1.txt", fileInfos[1].Name); - - fileInfos = zipInfo.GetFiles("testinfo1.txt"); - Assert.IsNotNull(fileInfos); - Assert.AreEqual(1, fileInfos.Count); - Assert.AreEqual("testinfo1.txt", fileInfos[0].Name); - Assert.IsTrue(DateTime.Now - fileInfos[0].LastWriteTime < TimeSpan.FromMinutes(1), - "Checking ZipFileInfo.LastWriteTime is current."); - } - - [TestMethod] - //[Ignore] // Requires clean environment. - public void ZipInfoNullParams() - { - int fileCount = 10, fileSize = 1024; - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = "zipinfo-" + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - - ZipInfo zipInfo = new ZipInfo("testnull.zip"); - - CompressionTestUtil.TestArchiveInfoNullParams(zipInfo, dirA, dirB, files); - } - - [TestMethod] - public void ZipFileInfoNullParams() - { - Exception caughtEx; - ZipInfo zipInfo = new ZipInfo("test.zip"); - int txtSize = 10240; - CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); - zipInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); - ZipFileInfo zfi = new ZipFileInfo(zipInfo, "test01.txt"); - - caughtEx = null; - try - { - new ZipFileInfo(null, "test00.txt"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - caughtEx = null; - try - { - new ZipFileInfo(zipInfo, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - caughtEx = null; - try - { - zfi.CopyTo(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); - } - - [TestMethod] - public void ZipEngineNullParams() - { - string[] testFiles = new string[] { "test.txt" }; - ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.zip", null, null); - - using (ZipEngine zipEngine = new ZipEngine()) - { - zipEngine.CompressionLevel = CompressionLevel.None; - - CompressionTestUtil.TestCompressionEngineNullParams(zipEngine, streamContext, testFiles); - } - } - - [TestMethod] - public void ZipBadPackStreamContexts() - { - string[] testFiles = new string[] { "test.txt" }; - CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000); - - using (ZipEngine zipEngine = new ZipEngine()) - { - zipEngine.CompressionLevel = CompressionLevel.None; - - CompressionTestUtil.TestBadPackStreamContexts(zipEngine, "test.zip", testFiles); - } - } - - [TestMethod] - public void ZipBadUnpackStreamContexts() - { - int txtSize = 40960; - ZipInfo zipInfo = new ZipInfo("test2.zip"); - CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, txtSize); - CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, txtSize); - zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null); - - using (ZipEngine zipEngine = new ZipEngine()) - { - CompressionTestUtil.TestBadUnpackStreamContexts(zipEngine, "test2.zip"); - } - } - - [TestMethod] - [Ignore] // Failed on build server, need to investigate. - public void ZipTruncatedArchive() - { - ZipInfo zipInfo = new ZipInfo("test-t.zip"); - CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, 5); - CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, 5); - zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null); - - CompressionTestUtil.TestTruncatedArchive(zipInfo, typeof(ZipException)); - } - - /* - [TestMethod] - public void ZipUnpack() - { - IList fileInfos; - foreach (FileInfo zipFile in new DirectoryInfo("D:\\temp").GetFiles("*.zip")) - { - Console.WriteLine("====================================================="); - Console.WriteLine(zipFile.FullName); - Console.WriteLine("====================================================="); - ZipInfo zipTest = new ZipInfo(zipFile.FullName); - fileInfos = zipTest.GetFiles(); - Assert.AreNotEqual(0, fileInfos.Count); - foreach (ArchiveFileInfo file in fileInfos) - { - Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime); - } - - Directory.CreateDirectory(Path.GetFileNameWithoutExtension(zipFile.Name)); - zipTest.Unpack(Path.GetFileNameWithoutExtension(zipFile.Name)); - } - } - */ - - /* - [TestMethod] - public void ZipUnpackSelfExtractor() - { - ZipInfo zipTest = new ZipInfo(@"C:\temp\testzip.exe"); - IList fileInfos = zipTest.GetFiles(); - Assert.AreNotEqual(0, fileInfos.Count); - foreach (ArchiveFileInfo file in fileInfos) - { - Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime); - } - - string extractDir = Path.GetFileNameWithoutExtension(zipTest.Name); - Directory.CreateDirectory(extractDir); - zipTest.Unpack(extractDir); - } - */ - - private const string TEST_FILENAME_PREFIX = "\x20AC"; - - private IList RunZipPackUnpack(int fileCount, long fileSize, - long maxArchiveSize) - { - return this.RunZipPackUnpack(fileCount, fileSize, maxArchiveSize, CompressionLevel.Normal); - } - - private IList RunZipPackUnpack(int fileCount, long fileSize, - long maxArchiveSize, CompressionLevel compLevel) - { - Console.WriteLine("Creating zip archive with {0} files of size {1}", - fileCount, fileSize); - Console.WriteLine("MaxArchiveSize={0}, CompressionLevel={1}", maxArchiveSize, compLevel); - - string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); - if (Directory.Exists(dirA)) Directory.Delete(dirA, true); - Directory.CreateDirectory(dirA); - string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); - if (Directory.Exists(dirB)) Directory.Delete(dirB, true); - Directory.CreateDirectory(dirB); - - string[] files = new string[fileCount]; - for (int iFile = 0; iFile < fileCount; iFile++) - { - files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt"; - CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); - } - - string[] archiveNames = new string[1000]; - for (int i = 0; i < archiveNames.Length; i++) - { - if (i < 100) - { - archiveNames[i] = String.Format( - (i == 0 ? "{0}-{1}.zip" : "{0}-{1}.z{2:d02}"), - fileCount, fileSize, i); - } - else - { - archiveNames[i] = String.Format( - "{0}-{1}.{2:d03}", fileCount, fileSize, i); - } - } - - string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize); - CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile); - - IList fileInfo; - using (ZipEngine zipEngine = new ZipEngine()) - { - zipEngine.CompressionLevel = compLevel; - - File.AppendAllText(progressTextFile, - "\r\n\r\n====================================================\r\nCREATE\r\n\r\n"); - zipEngine.Progress += testUtil.PrintArchiveProgress; - - OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null); - streamContext.OptionHandler = - delegate(string optionName, object[] parameters) - { - // For testing purposes, force zip64 for only moderately large files. - switch (optionName) - { - case "forceZip64": - return fileSize > UInt16.MaxValue; - default: - return null; - } - }; - - zipEngine.Pack(streamContext, files, maxArchiveSize); - - string checkArchiveName = archiveNames[0]; - if (File.Exists(archiveNames[1])) checkArchiveName = archiveNames[1]; - using (Stream archiveStream = File.OpenRead(checkArchiveName)) - { - bool isArchive = zipEngine.IsArchive(archiveStream); - Assert.IsTrue(isArchive, "Checking that created archive appears valid."); - } - - IList createdArchiveNames = new List(archiveNames.Length); - for (int i = 0; i < archiveNames.Length; i++) - { - if (File.Exists(archiveNames[i])) - { - createdArchiveNames.Add(archiveNames[i]); - } - else - { - break; - } - } - - Assert.AreNotEqual(0, createdArchiveNames.Count); - - Console.WriteLine("Listing zip archive with {0} files of size {1}", - fileCount, fileSize); - File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n"); - fileInfo = zipEngine.GetFileInfo( - new ArchiveFileStreamContext(createdArchiveNames, null, null), null); - - Assert.AreEqual(fileCount, fileInfo.Count); - - Console.WriteLine("Extracting zip archive with {0} files of size {1}", - fileCount, fileSize); - File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n"); - zipEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null); - } - - bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); - Assert.IsTrue(directoryMatch, - "Testing whether zip output directory matches input directory."); - - return fileInfo; - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs b/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs deleted file mode 100644 index e7a5373d..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs +++ /dev/null @@ -1,649 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System; -using System.IO; -using System.Text; -using System.Collections.Generic; -using System.Security.Cryptography; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using WixToolset.Dtf.Compression; - -namespace WixToolset.Dtf.Test -{ - public class CompressionTestUtil - { - private static MD5 md5 = new MD5CryptoServiceProvider(); - - private string progressTextFile; - - public CompressionTestUtil(string progressTextFile) - { - this.progressTextFile = progressTextFile; - } - - public static IList ExpectedProgress - { - get { return CompressionTestUtil.expectedProgress; } - set { CompressionTestUtil.expectedProgress = value; } - } - private static IList expectedProgress; - - public void PrintArchiveProgress(object source, ArchiveProgressEventArgs e) - { - switch (e.ProgressType) - { - case ArchiveProgressType.StartFile: - { - Console.WriteLine("StartFile: {0}", e.CurrentFileName); - } break; - case ArchiveProgressType.FinishFile: - { - Console.WriteLine("FinishFile: {0}", e.CurrentFileName); - } break; - case ArchiveProgressType.StartArchive: - { - Console.WriteLine("StartArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName); - } break; - case ArchiveProgressType.FinishArchive: - { - Console.WriteLine("FinishArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName); - } break; - } - - File.AppendAllText(this.progressTextFile, e.ToString().Replace("\n", Environment.NewLine)); - - if (CompressionTestUtil.expectedProgress != null && - e.ProgressType != ArchiveProgressType.PartialFile && - e.ProgressType != ArchiveProgressType.PartialArchive) - { - Assert.AreNotEqual(0, CompressionTestUtil.expectedProgress.Count); - int[] expected = CompressionTestUtil.expectedProgress[0]; - CompressionTestUtil.expectedProgress.RemoveAt(0); - Assert.AreEqual((ArchiveProgressType) expected[0], e.ProgressType, "Checking ProgressType."); - Assert.AreEqual(expected[1], e.CurrentFileNumber, "Checking CurrentFileNumber."); - Assert.AreEqual(expected[2], e.TotalFiles, "Checking TotalFiles."); - Assert.AreEqual(expected[4], e.CurrentArchiveNumber, "Checking CurrentArchiveNumber."); - Assert.AreEqual(expected[5], e.TotalArchives, "Checking TotalArchives."); - } - } - - public static bool CompareDirectories(string dirA, string dirB) - { - bool difference = false; - Console.WriteLine("Comparing directories {0}, {1}", dirA, dirB); - - string[] filesA = Directory.GetFiles(dirA); - string[] filesB = Directory.GetFiles(dirB); - for (int iA = 0; iA < filesA.Length; iA++) - { - filesA[iA] = Path.GetFileName(filesA[iA]); - } - for (int iB = 0; iB < filesB.Length; iB++) - { - filesB[iB] = Path.GetFileName(filesB[iB]); - } - Array.Sort(filesA); - Array.Sort(filesB); - - for (int iA = 0, iB = 0; iA < filesA.Length || iB < filesB.Length; ) - { - int comp; - if (iA == filesA.Length) - { - comp = 1; - } - else if (iB == filesB.Length) - { - comp = -1; - } - else - { - comp = String.Compare(filesA[iA], filesB[iB]); - } - if (comp < 0) - { - Console.WriteLine("< " + filesA[iA]); - difference = true; - iA++; - } - else if (comp > 0) - { - Console.WriteLine("> " + filesB[iB]); - difference = true; - iB++; - } - else - { - string fileA = Path.Combine(dirA, filesA[iA]); - string fileB = Path.Combine(dirB, filesB[iB]); - - byte[] hashA; - byte[] hashB; - - lock (CompressionTestUtil.md5) - { - using (Stream fileAStream = File.OpenRead(fileA)) - { - hashA = CompressionTestUtil.md5.ComputeHash(fileAStream); - } - using (Stream fileBStream = File.OpenRead(fileB)) - { - hashB = CompressionTestUtil.md5.ComputeHash(fileBStream); - } - } - - for (int i = 0; i < hashA.Length; i++) - { - if (hashA[i] != hashB[i]) - { - Console.WriteLine("~ " + filesA[iA]); - difference = true; - break; - } - } - - iA++; - iB++; - } - } - - string[] dirsA = Directory.GetDirectories(dirA); - string[] dirsB = Directory.GetDirectories(dirB); - for (int iA = 0; iA < dirsA.Length; iA++) - { - dirsA[iA] = Path.GetFileName(dirsA[iA]); - } - for (int iB = 0; iB < dirsB.Length; iB++) - { - dirsB[iB] = Path.GetFileName(dirsB[iB]); - } - Array.Sort(dirsA); - Array.Sort(dirsB); - - for (int iA = 0, iB = 0; iA < dirsA.Length || iB < dirsB.Length; ) - { - int comp; - if (iA == dirsA.Length) - { - comp = 1; - } - else if (iB == dirsB.Length) - { - comp = -1; - } - else - { - comp = String.Compare(dirsA[iA], dirsB[iB]); - } - if (comp < 0) - { - Console.WriteLine("< {0}\\", dirsA[iA]); - difference = true; - iA++; - } - else if (comp > 0) - { - Console.WriteLine("> {1}\\", dirsB[iB]); - difference = true; - iB++; - } - else - { - string subDirA = Path.Combine(dirA, dirsA[iA]); - string subDirB = Path.Combine(dirB, dirsB[iB]); - if (!CompressionTestUtil.CompareDirectories(subDirA, subDirB)) - { - difference = true; - } - iA++; - iB++; - } - } - - return !difference; - } - - - public static void GenerateRandomFile(string path, int seed, long size) - { - Console.WriteLine("Generating random file {0} (seed={1}, size={2})", - path, seed, size); - Random random = new Random(seed); - bool easy = random.Next(2) == 1; - int chunk = 1024 * random.Next(1, 100); - using (TextWriter tw = new StreamWriter( - File.Create(path, 4096), Encoding.ASCII)) - { - for (long count = 0; count < size; count++) - { - char c = (char) (easy ? random.Next('a', 'b' + 1) - : random.Next(32, 127)); - tw.Write(c); - if (--chunk == 0) - { - chunk = 1024 * random.Next(1, 101); - easy = random.Next(2) == 1; - } - } - } - } - - public static void TestArchiveInfoNullParams( - ArchiveInfo archiveInfo, - string dirA, - string dirB, - string[] files) - { - Exception caughtEx = null; - try - { - archiveInfo.PackFiles(null, null, files); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.PackFiles(null, files, new string[] { }); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.PackFileSet(dirA, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.PackFiles(null, files, files); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.PackFiles(dirA, null, files); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.PackFiles(dirA, files, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); - - caughtEx = null; - try - { - archiveInfo.CopyTo(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.CopyTo(null, true); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.MoveTo(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.GetFiles(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFile(null, "test.txt"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFile("test.txt", null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFiles(null, dirB, files); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFiles(files, null, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFiles(files, null, files); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFiles(files, dirB, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFiles(files, dirB, new string[] { }); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - archiveInfo.UnpackFileSet(null, dirB); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - } - - public static void TestCompressionEngineNullParams( - CompressionEngine engine, - ArchiveFileStreamContext streamContext, - string[] testFiles) - { - Exception caughtEx; - - Console.WriteLine("Testing null streamContext."); - caughtEx = null; - try - { - engine.Pack(null, testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.Pack(null, testFiles, 0); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - - Console.WriteLine("Testing null files."); - caughtEx = null; - try - { - engine.Pack(streamContext, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - - Console.WriteLine("Testing null files."); - caughtEx = null; - try - { - engine.Pack(streamContext, null, 0); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - - - Console.WriteLine("Testing null stream."); - caughtEx = null; - try - { - engine.IsArchive(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.FindArchiveOffset(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.GetFiles(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.GetFileInfo(null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.Unpack(null, "testUnpack.txt"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - Console.WriteLine("Testing null streamContext."); - caughtEx = null; - try - { - engine.GetFiles(null, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.GetFileInfo(null, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - caughtEx = null; - try - { - engine.Unpack((IUnpackStreamContext) null, null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); - } - - public static void TestBadPackStreamContexts( - CompressionEngine engine, string archiveName, string[] testFiles) - { - Exception caughtEx; - - Console.WriteLine("Testing streamContext that returns null from GetName."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, false, false, true, true, true, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that returns null from OpenArchive."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that returns null from OpenFile."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on GetName."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, true, false, true, true, true, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on OpenArchive."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on CloseArchive."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on OpenFile."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on CloseFile."); - caughtEx = null; - try - { - engine.Pack( - new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), - testFiles); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - } - - public static void TestBadUnpackStreamContexts( - CompressionEngine engine, string archiveName) - { - Exception caughtEx; - - Console.WriteLine("Testing streamContext that returns null from OpenArchive."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that returns null from OpenFile."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on OpenArchive."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on CloseArchive."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on OpenFile."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - Console.WriteLine("Testing streamContext that throws on CloseFile."); - caughtEx = null; - try - { - engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), null); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); - } - - public static void TestTruncatedArchive( - ArchiveInfo archiveInfo, Type expectedExceptionType) - { - for (long len = archiveInfo.Length - 1; len >= 0; len--) - { - string testArchive = String.Format("{0}.{1:d06}", - archiveInfo.FullName, len); - if (File.Exists(testArchive)) - { - File.Delete(testArchive); - } - - archiveInfo.CopyTo(testArchive); - using (FileStream truncateStream = - File.Open(testArchive, FileMode.Open, FileAccess.ReadWrite)) - { - truncateStream.SetLength(len); - } - - ArchiveInfo testArchiveInfo = (ArchiveInfo) archiveInfo.GetType() - .GetConstructor(new Type[] { typeof(string) }).Invoke(new object[] { testArchive }); - - Exception caughtEx = null; - try - { - testArchiveInfo.GetFiles(); - } - catch (Exception ex) { caughtEx = ex; } - File.Delete(testArchive); - - if (caughtEx != null) - { - Assert.IsInstanceOfType(caughtEx, expectedExceptionType, - String.Format("Caught exception listing archive truncated to {0}/{1} bytes", - len, archiveInfo.Length)); - } - } - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs b/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs deleted file mode 100644 index 2531f3bc..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System; -using System.IO; -using System.Collections.Generic; -using WixToolset.Dtf.Compression; - -namespace WixToolset.Dtf.Test -{ - public class MisbehavingStreamContext : ArchiveFileStreamContext - { - public const string EXCEPTION = "Test exception."; - - private bool throwEx; - private bool getName; - private bool openArchive; - private bool closeArchive; - private bool openFile; - private bool closeFile; - private int closeFileCount; - - public MisbehavingStreamContext( - string cabinetFile, - string directory, - IDictionary files, - bool throwEx, - bool getName, - bool openArchive, - bool closeArchive, - bool openFile, - bool closeFile) - : base(cabinetFile, directory, files) - { - this.throwEx = throwEx; - this.getName = getName; - this.openArchive = openArchive; - this.closeArchive = closeArchive; - this.openFile = openFile; - this.closeFile = closeFile; - } - - public override string GetArchiveName(int archiveNumber) - { - if (!this.getName) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - else - { - return null; - } - } - return base.GetArchiveName(archiveNumber); - } - - public override Stream OpenArchiveWriteStream( - int archiveNumber, - string archiveName, - bool truncate, - CompressionEngine compressionEngine) - { - if (!this.openArchive) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - else - { - return null; - } - } - return base.OpenArchiveWriteStream( - archiveNumber, archiveName, truncate, compressionEngine); - } - - public override void CloseArchiveWriteStream( - int archiveNumber, - string archiveName, - Stream stream) - { - if (!this.closeArchive) - { - if (throwEx) - { - this.closeArchive = true; - throw new Exception(EXCEPTION); - } - return; - } - base.CloseArchiveWriteStream(archiveNumber, archiveName, stream); - } - - public override Stream OpenFileReadStream( - string path, - out FileAttributes attributes, - out DateTime lastWriteTime) - { - if (!this.openFile) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - else - { - attributes = FileAttributes.Normal; - lastWriteTime = DateTime.MinValue; - return null; - } - } - return base.OpenFileReadStream(path, out attributes, out lastWriteTime); - } - - public override void CloseFileReadStream(string path, Stream stream) - { - if (!this.closeFile && ++closeFileCount == 2) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - return; - } - base.CloseFileReadStream(path, stream); - } - - public override Stream OpenArchiveReadStream( - int archiveNumber, - string archiveName, - CompressionEngine compressionEngine) - { - if (!this.openArchive) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - else - { - return null; - } - } - return base.OpenArchiveReadStream(archiveNumber, archiveName, compressionEngine); - } - - public override void CloseArchiveReadStream( - int archiveNumber, - string archiveName, - Stream stream) - { - if (!this.closeArchive) - { - if (throwEx) - { - this.closeArchive = true; - throw new Exception(EXCEPTION); - } - return; - } - base.CloseArchiveReadStream(archiveNumber, archiveName, stream); - } - - public override Stream OpenFileWriteStream( - string path, - long fileSize, - DateTime lastWriteTime) - { - if (!this.openFile) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - else - { - return null; - } - } - return base.OpenFileWriteStream(path, fileSize, lastWriteTime); - } - - public override void CloseFileWriteStream( - string path, - Stream stream, - FileAttributes attributes, - DateTime lastWriteTime) - { - if (!this.closeFile && ++closeFileCount == 2) - { - if (throwEx) - { - throw new Exception(EXCEPTION); - } - return; - } - base.CloseFileWriteStream(path, stream, attributes, lastWriteTime); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs b/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs deleted file mode 100644 index 98354d97..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System; -using System.Collections.Generic; -using WixToolset.Dtf.Compression; - -namespace WixToolset.Dtf.Test -{ - public class OptionStreamContext : ArchiveFileStreamContext - { - private PackOptionHandler packOptionHandler; - - public OptionStreamContext(IList archiveFiles, string directory, IDictionary files) - : base(archiveFiles, directory, files) - { - } - - public delegate object PackOptionHandler(string optionName, object[] parameters); - - public PackOptionHandler OptionHandler - { - get - { - return this.packOptionHandler; - } - set - { - this.packOptionHandler = value; - } - } - - public override object GetOption(string optionName, object[] parameters) - { - if (this.OptionHandler == null) - { - return null; - } - - return this.OptionHandler(optionName, parameters); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj b/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj deleted file mode 100644 index 194628a7..00000000 --- a/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - - {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} - Library - WixToolsetTests.Dtf - WixToolsetTests.Dtf.Compression - false - false - v4.7.2 - - - - - - - - - - - - - - - - - - {45D81DAB-0559-4836-8106-CE9987FD4AB5} - WixToolset.Dtf.Compression - - - - - - diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs deleted file mode 100644 index bf843024..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Text; - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.WindowsInstaller; - - [TestClass] - public class CustomActionTest - { - public CustomActionTest() - { - } - - [TestMethod] - [Ignore] // Currently fails. - public void CustomActionTest1() - { - InstallLogModes logEverything = - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog; - - Installer.SetInternalUI(InstallUIOptions.Silent); - ExternalUIHandler prevHandler = Installer.SetExternalUI( - WindowsInstallerTest.ExternalUILogger, logEverything); - - try - { - string[] customActions = new string[] { "SampleCA1", "SampleCA2" }; - #if DEBUG - string caDir = @"..\..\..\..\..\build\debug\x86\"; - #else - string caDir = @"..\..\..\..\..\build\ship\x86\"; - #endif - caDir = Path.GetFullPath(caDir); - string caFile = "WixToolset.Dtf.Samples.ManagedCA.dll"; - string caProduct = "CustomActionTest.msi"; - - this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, false); - - Exception caughtEx = null; - try - { - Installer.InstallProduct(caProduct, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException), - "Exception thrown while installing product: " + caughtEx); - - string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - string arch2 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); - if (arch == "AMD64" || arch2 == "AMD64") - { - caDir = caDir.Replace("x86", "x64"); - - this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, true); - - caughtEx = null; - try - { - Installer.InstallProduct(caProduct, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException), - "Exception thrown while installing 64bit product: " + caughtEx); - } - } - finally - { - Installer.SetExternalUI(prevHandler, InstallLogModes.None); - } - } - - private void CreateCustomActionProduct( - string msiFile, string customActionFile, IList customActions, bool sixtyFourBit) - { - using (Database db = new Database(msiFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db, sixtyFourBit); - WindowsInstallerUtils.CreateTestProduct(db); - - if (!File.Exists(customActionFile)) - throw new FileNotFoundException(customActionFile); - - using (Record binRec = new Record(2)) - { - binRec[1] = Path.GetFileName(customActionFile); - binRec.SetStream(2, customActionFile); - - db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec); - } - - using (Record binRec2 = new Record(2)) - { - binRec2[1] = "TestData"; - binRec2.SetStream(2, new MemoryStream(Encoding.UTF8.GetBytes("This is a test data stream."))); - - db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec2); - } - - for (int i = 0; i < customActions.Count; i++) - { - db.Execute( - "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('{0}', 1, '{1}', '{2}')", - customActions[i], - Path.GetFileName(customActionFile), - customActions[i]); - db.Execute( - "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('{0}', '', {1})", - customActions[i], - 101 + i); - } - - db.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES ('SampleCATest', 'TestValue')"); - - db.Commit(); - } - } - - [TestMethod] - public void CustomActionData() - { - string dataString = "Key1=Value1;Key2=;Key3;Key4=Value=4;Key5"; - string dataString2 = "Key1=;Key2=Value2;Key3;Key4;Key6=Value;;6=6;Key7=Value7"; - - CustomActionData data = new CustomActionData(dataString); - Assert.AreEqual(dataString, data.ToString()); - - data["Key1"] = String.Empty; - data["Key2"] = "Value2"; - data["Key4"] = null; - data.Remove("Key5"); - data["Key6"] = "Value;6=6"; - data["Key7"] = "Value7"; - - Assert.AreEqual(dataString2, data.ToString()); - - MyDataClass myData = new MyDataClass(); - myData.Member1 = "test1"; - myData.Member2 = "test2"; - data.AddObject("MyData", myData); - - string myDataString = data.ToString(); - CustomActionData data2 = new CustomActionData(myDataString); - - MyDataClass myData2 = data2.GetObject("MyData"); - Assert.AreEqual(myData, myData2); - - List myComplexDataObject = new List(); - myComplexDataObject.Add("CValue1"); - myComplexDataObject.Add("CValue2"); - myComplexDataObject.Add("CValue3"); - - CustomActionData myComplexData = new CustomActionData(); - myComplexData.AddObject("MyComplexData", myComplexDataObject); - myComplexData.AddObject("NestedData", data); - string myComplexDataString = myComplexData.ToString(); - - CustomActionData myComplexData2 = new CustomActionData(myComplexDataString); - List myComplexDataObject2 = myComplexData2.GetObject>("MyComplexData"); - - Assert.AreEqual(myComplexDataObject.Count, myComplexDataObject2.Count); - for (int i = 0; i < myComplexDataObject.Count; i++) - { - Assert.AreEqual(myComplexDataObject[i], myComplexDataObject2[i]); - } - - data2 = myComplexData2.GetObject("NestedData"); - Assert.AreEqual(data.ToString(), data2.ToString()); - } - - public class MyDataClass - { - public string Member1; - public string Member2; - - public override bool Equals(object obj) - { - MyDataClass other = obj as MyDataClass; - return other != null && this.Member1 == other.Member1 && this.Member2 == other.Member2; - } - - public override int GetHashCode() - { - return (this.Member1 != null ? this.Member1.GetHashCode() : 0) ^ - (this.Member2 != null ? this.Member2.GetHashCode() : 0); - } - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj deleted file mode 100644 index 27e0b499..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - {137D376B-989F-4FEA-9A67-01D8D38CA0DE} - Library - WixToolsetTests.Dtf - WixToolsetTests.Dtf.WindowsInstaller.CustomActions - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - false - false - v4.7.2 - - - - - - - - - - - - - {16F5202F-9276-4166-975C-C9654BAF8012} - WixToolsetTests.Dtf.WindowsInstaller - - - - - - diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs deleted file mode 100644 index 7776a1c3..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System; -using System.Text; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using WixToolset.Dtf.WindowsInstaller; -using WixToolset.Dtf.WindowsInstaller.Linq; - -namespace WixToolset.Dtf.Test -{ - [TestClass] - public class LinqTest - { - private void InitLinqTestDatabase(QDatabase db) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - db.Execute( - "INSERT INTO `Feature` (`Feature`, `Title`, `Description`, `Level`, `Attributes`) VALUES ('{0}', '{1}', '{2}', {3}, {4})", - "TestFeature2", - "Test Feature 2", - "Test Feature 2 Description", - 1, - (int) FeatureAttributes.None); - - WindowsInstallerUtils.AddRegistryComponent( - db, "TestFeature2", "MyTestRegComp", - Guid.NewGuid().ToString("B"), - "SOFTWARE\\Microsoft\\DTF\\Test", - "MyTestRegComp", "test"); - WindowsInstallerUtils.AddRegistryComponent( - db, "TestFeature2", "MyTestRegComp2", - Guid.NewGuid().ToString("B"), - "SOFTWARE\\Microsoft\\DTF\\Test", - "MyTestRegComp2", "test2"); - WindowsInstallerUtils.AddRegistryComponent( - db, "TestFeature2", "excludeComp", - Guid.NewGuid().ToString("B"), - "SOFTWARE\\Microsoft\\DTF\\Test", - "MyTestRegComp3", "test3"); - - db.Commit(); - - db.Log = Console.Out; - } - - [TestMethod] - public void LinqSimple() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - var comps = from c in db.Components - select c; - - int count = 0; - foreach (var c in comps) - { - Console.WriteLine(c); - count++; - } - - Assert.AreEqual(4, count); - } - } - - [TestMethod] - public void LinqWhereNull() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - var features = from f in db.Features - where f.Description != null - select f; - - int count = 0; - foreach (var f in features) - { - Console.WriteLine(f); - Assert.AreEqual("TestFeature2", f.Feature); - count++; - } - - Assert.AreEqual(1, count); - - var features2 = from f in db.Features - where f.Description == null - select f; - - count = 0; - foreach (var f in features2) - { - Console.WriteLine(f); - Assert.AreEqual("TestFeature1", f.Feature); - count++; - } - - Assert.AreEqual(1, count); - } - } - - [TestMethod] - public void LinqWhereOperators() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - for (int i = 0; i < 100; i++) - { - var newFile = db.Files.NewRecord(); - newFile.File = "TestFile" + i; - newFile.Component_ = "TestComponent"; - newFile.FileName = "TestFile" + i + ".txt"; - newFile.FileSize = i % 10; - newFile.Sequence = i; - newFile.Insert(); - } - - var files1 = from f in db.Files where f.Sequence < 40 select f; - Assert.AreEqual(40, files1.AsEnumerable().Count()); - - var files2 = from f in db.Files where f.Sequence <= 40 select f; - Assert.AreEqual(41, files2.AsEnumerable().Count()); - - var files3 = from f in db.Files where f.Sequence > 40 select f; - Assert.AreEqual(59, files3.AsEnumerable().Count()); - - var files4 = from f in db.Files where f.Sequence >= 40 select f; - Assert.AreEqual(60, files4.AsEnumerable().Count()); - - var files5 = from f in db.Files where 40 < f.Sequence select f; - Assert.AreEqual(59, files5.AsEnumerable().Count()); - - var files6 = from f in db.Files where 40 <= f.Sequence select f; - Assert.AreEqual(60, files6.AsEnumerable().Count()); - - var files7 = from f in db.Files where 40 > f.Sequence select f; - Assert.AreEqual(40, files7.AsEnumerable().Count()); - - var files8 = from f in db.Files where 40 >= f.Sequence select f; - Assert.AreEqual(41, files8.AsEnumerable().Count()); - - var files9 = from f in db.Files where f.Sequence == 40 select f; - Assert.AreEqual(40, files9.AsEnumerable().First().Sequence); - - var files10 = from f in db.Files where f.Sequence != 40 select f; - Assert.AreEqual(99, files10.AsEnumerable().Count()); - - var files11 = from f in db.Files where 40 == f.Sequence select f; - Assert.AreEqual(40, files11.AsEnumerable().First().Sequence); - - var files12 = from f in db.Files where 40 != f.Sequence select f; - Assert.AreEqual(99, files12.AsEnumerable().Count()); - } - } - - [TestMethod] - public void LinqShapeSelect() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - Console.WriteLine("Running LINQ query 1."); - var features1 = from f in db.Features - select new { Name = f.Feature, - Desc = f.Description }; - - int count = 0; - foreach (var f in features1) - { - Console.WriteLine(f); - count++; - } - - Assert.AreEqual(2, count); - - Console.WriteLine(); - Console.WriteLine("Running LINQ query 2."); - var features2 = from f in db.Features - where f.Description != null - select new { Name = f.Feature, - Desc = f.Description.ToLower() }; - - count = 0; - foreach (var f in features2) - { - Console.WriteLine(f); - Assert.AreEqual("TestFeature2", f.Name); - count++; - } - - Assert.AreEqual(1, count); - } - } - - [TestMethod] - public void LinqUpdateNullableString() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - string newDescription = "New updated feature description."; - - var features = from f in db.Features - where f.Description != null - select f; - - int count = 0; - foreach (var f in features) - { - Console.WriteLine(f); - Assert.AreEqual("TestFeature2", f.Feature); - f.Description = newDescription; - count++; - } - - Assert.AreEqual(1, count); - - var features2 = from f in db.Features - where f.Description == newDescription - select f; - count = 0; - foreach (var f in features2) - { - Console.WriteLine(f); - Assert.AreEqual("TestFeature2", f.Feature); - f.Description = null; - count++; - } - - Assert.AreEqual(1, count); - - var features3 = from f in db.Features - where f.Description == null - select f.Feature; - count = 0; - foreach (var f in features3) - { - Console.WriteLine(f); - count++; - } - - Assert.AreEqual(2, count); - - db.Commit(); - } - } - - [TestMethod] - public void LinqInsertDelete() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - var newProp = db.Properties.NewRecord(); - newProp.Property = "TestNewProp1"; - newProp.Value = "TestNewValue"; - newProp.Insert(); - - string prop = (from p in db.Properties - where p.Property == "TestNewProp1" - select p.Value).AsEnumerable().First(); - Assert.AreEqual("TestNewValue", prop); - - newProp.Delete(); - - int propCount = (from p in db.Properties - where p.Property == "TestNewProp1" - select p.Value).AsEnumerable().Count(); - Assert.AreEqual(0, propCount); - - db.Commit(); - } - } - - [TestMethod] - public void LinqQueryQRecord() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - var installFilesSeq = (from a in db["InstallExecuteSequence"] - where a["Action"] == "InstallFiles" - select a["Sequence"]).AsEnumerable().First(); - Assert.AreEqual("4000", installFilesSeq); - } - } - - [TestMethod] - public void LinqOrderBy() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - - var actions = from a in db.InstallExecuteSequences - orderby a.Sequence - select a.Action; - foreach (var a in actions) - { - Console.WriteLine(a); - } - - var files = from f in db.Files - orderby f.FileSize, f["Sequence"] - where f.Attributes == FileAttributes.None - select f; - - foreach (var f in files) - { - Console.WriteLine(f); - } - } - } - - [TestMethod] - public void LinqTwoWayJoin() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - int count; - - var regs = from r in db.Registries - join c in db["Component"] on r.Component_ equals c["Component"] - where c["Component"] == "MyTestRegComp" && - r.Root == RegistryRoot.UserOrMachine - select new { Reg = r.Registry, Dir = c["Directory_"] }; - - count = 0; - foreach (var r in regs) - { - Console.WriteLine(r); - count++; - } - Assert.AreEqual(1, count); - - var regs2 = from r in db.Registries - join c in db.Components on r.Component_ equals c.Component - where c.Component == "MyTestRegComp" && - r.Root == RegistryRoot.UserOrMachine - select new { Reg = r, Dir = c.Directory_ }; - - count = 0; - foreach (var r in regs2) - { - Assert.IsNotNull(r.Reg.Registry); - Console.WriteLine(r); - count++; - } - Assert.AreEqual(1, count); - - var regs3 = from r in db.Registries - join c in db.Components on r.Component_ equals c.Component - where c.Component == "MyTestRegComp" && - r.Root == RegistryRoot.UserOrMachine - select r; - - count = 0; - foreach (var r in regs3) - { - Assert.IsNotNull(r.Registry); - Console.WriteLine(r); - count++; - } - Assert.AreEqual(1, count); - - } - } - - [TestMethod] - public void LinqFourWayJoin() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - int count; - - IList pretest = db.ExecuteStringQuery( - "SELECT `Feature`.`Feature` " + - "FROM `Feature`, `FeatureComponents`, `Component`, `Registry` " + - "WHERE `Feature`.`Feature` = `FeatureComponents`.`Feature_` " + - "AND `FeatureComponents`.`Component_` = `Component`.`Component` " + - "AND `Component`.`Component` = `Registry`.`Component_` " + - "AND (`Registry`.`Registry` = 'MyTestRegCompReg1')"); - Assert.AreEqual(1, pretest.Count); - - var features = from f in db.Features - join fc in db.FeatureComponents on f.Feature equals fc.Feature_ - join c in db.Components on fc.Component_ equals c.Component - join r in db.Registries on c.Component equals r.Component_ - where r.Registry == "MyTestRegCompReg1" - select f.Feature; - - count = 0; - foreach (var featureName in features) - { - Console.WriteLine(featureName); - count++; - } - Assert.AreEqual(1, count); - - } - } - - [TestMethod] - public void EnumTable() - { - using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) - { - this.InitLinqTestDatabase(db); - int count = 0; - foreach (var comp in db.Components) - { - Console.WriteLine(comp); - count++; - } - Assert.AreNotEqual(0, count); - } - } - - [TestMethod] - public void DatabaseAsQueryable() - { - using (Database db = new Database("testlinq.msi", DatabaseOpenMode.Create)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - var comps = from c in db.AsQueryable().Components - select c; - - int count = 0; - foreach (var c in comps) - { - Console.WriteLine(c); - count++; - } - - Assert.AreEqual(1, count); - } - } - - [TestMethod] - public void EnumProducts() - { - var products = from p in ProductInstallation.AllProducts - where p.Publisher == ".NET Foundation" - select new { Name = p.ProductName, - Ver = p.ProductVersion, - Company = p.Publisher, - InstallDate = p.InstallDate, - PackageCode = p.AdvertisedPackageCode }; - - foreach (var p in products) - { - Console.WriteLine(p); - Assert.IsTrue(p.Company == ".NET Foundation"); - } - } - - [TestMethod] - public void EnumFeatures() - { - foreach (var p in ProductInstallation.AllProducts) - { - Console.WriteLine(p.ProductName); - - foreach (var f in p.Features) - { - Console.WriteLine("\t" + f.FeatureName); - } - } - } - - [TestMethod] - public void EnumComponents() - { - var comps = from c in ComponentInstallation.AllComponents - where c.State == InstallState.Local && - c.Product.Publisher == ".NET Foundation" - select c.Path; - - int count = 0; - foreach (var c in comps) - { - if (++count == 100) break; - - Console.WriteLine(c); - } - - Assert.AreEqual(100, count); - } - } - -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj deleted file mode 100644 index a59e64d4..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - - {4F55F9B8-D8B6-41EB-8796-221B4CD98324} - Library - WixToolsetTests.Dtf - WixToolsetTests.Dtf.Linq - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - false - false - v4.7.2 - - - - - - - - - - - - - - - {85225597-5121-4361-8332-4E3246D5BBF5} - WixToolset.Dtf.WindowsInstaller - - - {7E66313B-C6D4-4729-8422-4D1474E0E6F7} - WixToolset.Dtf.WindowsInstaller.Linq - - - {16F5202F-9276-4166-975C-C9654BAF8012} - WixToolsetTests.Dtf.WindowsInstaller - - - - - - diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs deleted file mode 100644 index b0fc00a8..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Reflection; - using System.Windows.Forms; - using System.Globalization; - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.WindowsInstaller; - using View = WixToolset.Dtf.WindowsInstaller.View; - - [TestClass] - public class EmbeddedExternalUI - { - const InstallLogModes TestLogModes = - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData; - -#if DEBUG - const string EmbeddedUISampleBinDir = @"..\..\build\debug\"; -#else - const string EmbeddedUISampleBinDir = @"..\..\build\release\"; -#endif - - [TestMethod] - [Ignore] // Requires elevation. - public void EmbeddedUISingleInstall() - { - string dbFile = "EmbeddedUISingleInstall.msi"; - string productCode; - - string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); - string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; - - using (Record uiRec = new Record(5)) - { - uiRec[1] = "TestEmbeddedUI"; - uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; - uiRec[3] = 1; - uiRec[4] = (int) ( - EmbeddedExternalUI.TestLogModes | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); - db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); - } - - db.Commit(); - } - - Installer.SetInternalUI(InstallUIOptions.Full); - - ProductInstallation installation = new ProductInstallation(productCode); - Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); - - Exception caughtEx = null; - try - { - Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "install.log"); - Installer.InstallProduct(dbFile, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); - - Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine("==================================================================="); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - - try - { - Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "uninstall.log"); - Installer.InstallProduct(dbFile, "REMOVE=All"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while uninstalling product: " + caughtEx); - } - - // This test does not pass if run normally. - // It only passes when a failure is injected into the EmbeddedUI launcher. - ////[TestMethod] - public void EmbeddedUIInitializeFails() - { - string dbFile = "EmbeddedUIInitializeFails.msi"; - string productCode; - - string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); - string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; - - // A number that will be used to check whether a type 19 CA runs. - const string magicNumber = "3.14159265358979323846264338327950"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - const string failureActionName = "EmbeddedUIInitializeFails"; - db.Execute("INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) " + - "VALUES ('{0}', 19, '', 'Logging magic number: {1}')", failureActionName, magicNumber); - - // This type 19 CA (launch condition) is given a condition of 'UILevel = 3' so that it only runs if the - // installation is running in BASIC UI mode, which is what we expect if the EmbeddedUI fails to initialize. - db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) " + - "VALUES ('{0}', 'UILevel = 3', 1)", failureActionName); - - productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; - - using (Record uiRec = new Record(5)) - { - uiRec[1] = "TestEmbeddedUI"; - uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; - uiRec[3] = 1; - uiRec[4] = (int)( - EmbeddedExternalUI.TestLogModes | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); - db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); - } - - db.Commit(); - } - - Installer.SetInternalUI(InstallUIOptions.Full); - - ProductInstallation installation = new ProductInstallation(productCode); - Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); - - string logFile = "install.log"; - Exception caughtEx = null; - try - { - Installer.EnableLog(EmbeddedExternalUI.TestLogModes, logFile); - Installer.InstallProduct(dbFile, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsInstanceOfType(caughtEx, typeof(InstallerException), - "Excpected InstallerException installing product; caught: " + caughtEx); - - Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed."); - - string logText = File.ReadAllText(logFile); - Assert.IsTrue(logText.Contains(magicNumber), "Checking that the type 19 custom action ran."); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs deleted file mode 100644 index 26c172c9..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Collections.Generic; - using System.Text; - using WixToolset.Dtf.WindowsInstaller; - - public static class Schema - { - public static IList Tables - { - get - { - return new TableInfo[] - { - Binary, - Component, - CustomAction, - Directory, - EmbeddedUI, - Feature, - FeatureComponents, - File, - InstallExecuteSequence, - Media, - Property, - Registry - }; - } - } - - #region Table data - - public static TableInfo Binary { get { return new TableInfo( - "Binary", - new ColumnInfo[] - { - new ColumnInfo("Name", typeof(String), 72, true), - new ColumnInfo("Data", typeof(Stream), 0, true), - }, - new string[] { "Name" }); - } } - - public static TableInfo Component { get { return new TableInfo( - "Component", - new ColumnInfo[] - { - new ColumnInfo("Component", typeof(String), 72, true), - new ColumnInfo("ComponentId", typeof(String), 38, false), - new ColumnInfo("Directory_", typeof(String), 72, true), - new ColumnInfo("Attributes", typeof(Int16), 2, true), - new ColumnInfo("Condition", typeof(String), 255, false), - new ColumnInfo("KeyPath", typeof(String), 72, false), - }, - new string[] { "Component" }); - } } - - public static TableInfo CustomAction { get { return new TableInfo( - "CustomAction", - new ColumnInfo[] - { - new ColumnInfo("Action", typeof(String), 72, true), - new ColumnInfo("Type", typeof(Int16), 2, true), - new ColumnInfo("Source", typeof(String), 64, false), - new ColumnInfo("Target", typeof(String), 255, false), - }, - new string[] { "Action" }); - } } - - public static TableInfo Directory { get { return new TableInfo( - "Directory", - new ColumnInfo[] - { - new ColumnInfo("Directory", typeof(String), 72, true), - new ColumnInfo("Directory_Parent", typeof(String), 72, false), - new ColumnInfo("DefaultDir", typeof(String), 255, true, false, true), - }, - new string[] { "Directory" }); - } } - - public static TableInfo EmbeddedUI { get { return new TableInfo( - "MsiEmbeddedUI", - new ColumnInfo[] - { - new ColumnInfo("MsiEmbeddedUI", typeof(String), 72, true), - new ColumnInfo("FileName", typeof(String), 72, true), - new ColumnInfo("Attributes", typeof(Int16), 2, true), - new ColumnInfo("MessageFilter", typeof(Int32), 4, false), - new ColumnInfo("Data", typeof(Stream), 0, true), - }, - new string[] { "MsiEmbeddedUI" }); - } } - - public static TableInfo Feature { get { return new TableInfo( - "Feature", - new ColumnInfo[] - { - new ColumnInfo("Feature", typeof(String), 38, true), - new ColumnInfo("Feature_Parent", typeof(String), 38, false), - new ColumnInfo("Title", typeof(String), 64, false, false, true), - new ColumnInfo("Description", typeof(String), 64, false, false, true), - new ColumnInfo("Display", typeof(Int16), 2, false), - new ColumnInfo("Level", typeof(Int16), 2, true), - new ColumnInfo("Directory_", typeof(String), 72, false), - new ColumnInfo("Attributes", typeof(Int16), 2, true), - }, - new string[] { "Feature" }); - } } - - public static TableInfo FeatureComponents { get { return new TableInfo( - "FeatureComponents", - new ColumnInfo[] - { - new ColumnInfo("Feature_", typeof(String), 38, true), - new ColumnInfo("Component_", typeof(String), 72, true), - }, - new string[] { "Feature_", "Component_" }); - } } - - public static TableInfo File { get { return new TableInfo( - "File", - new ColumnInfo[] - { - new ColumnInfo("File", typeof(String), 72, true), - new ColumnInfo("Component_", typeof(String), 72, true), - new ColumnInfo("FileName", typeof(String), 255, true, false, true), - new ColumnInfo("FileSize", typeof(Int32), 4, true), - new ColumnInfo("Version", typeof(String), 72, false), - new ColumnInfo("Language", typeof(String), 20, false), - new ColumnInfo("Attributes", typeof(Int16), 2, false), - new ColumnInfo("Sequence", typeof(Int16), 2, true), - }, - new string[] { "File" }); - } } - - public static TableInfo InstallExecuteSequence { get { return new TableInfo( - "InstallExecuteSequence", - new ColumnInfo[] - { - new ColumnInfo("Action", typeof(String), 72, true), - new ColumnInfo("Condition", typeof(String), 255, false), - new ColumnInfo("Sequence", typeof(Int16), 2, true), - }, - new string[] { "Action" }); - } } - - public static TableInfo Media { get { return new TableInfo( - "Media", - new ColumnInfo[] - { - new ColumnInfo("DiskId", typeof(Int16), 2, true), - new ColumnInfo("LastSequence", typeof(Int16), 2, true), - new ColumnInfo("DiskPrompt", typeof(String), 64, false, false, true), - new ColumnInfo("Cabinet", typeof(String), 255, false), - new ColumnInfo("VolumeLabel", typeof(String), 32, false), - new ColumnInfo("Source", typeof(String), 32, false), - }, - new string[] { "DiskId" }); - } } - - public static TableInfo Property { get { return new TableInfo( - "Property", - new ColumnInfo[] - { - new ColumnInfo("Property", typeof(String), 72, true), - new ColumnInfo("Value", typeof(String), 255, true), - }, - new string[] { "Property" }); - } } - - public static TableInfo Registry { get { return new TableInfo( - "Registry", - new ColumnInfo[] - { - new ColumnInfo("Registry", typeof(String), 72, true), - new ColumnInfo("Root", typeof(Int16), 2, true), - new ColumnInfo("Key", typeof(String), 255, true, false, true), - new ColumnInfo("Name", typeof(String), 255, false, false, true), - new ColumnInfo("Value", typeof(String), 0, false, false, true), - new ColumnInfo("Component_", typeof(String), 72, true), - }, - new string[] { "Registry" }); - } } - - #endregion - - } - - public class Action - { - public readonly string Name; - public readonly int Sequence; - - public Action(string name, int sequence) - { - this.Name = name; - this.Sequence = sequence; - } - - } - - public class Sequence - { - public static IList InstallExecute - { - get - { - return new Action[] - { - new Action("CostInitialize", 800), - new Action("FileCost", 900), - new Action("CostFinalize", 1000), - new Action("InstallValidate", 1400), - new Action("InstallInitialize", 1500), - new Action("ProcessComponents", 1600), - new Action("UnpublishComponents", 1700), - new Action("UnpublishFeatures", 1800), - new Action("RemoveRegistryValues", 2600), - new Action("RemoveFiles", 3500), - new Action("RemoveFolders", 3600), - new Action("CreateFolders", 3700), - new Action("MoveFiles", 3800), - new Action("InstallFiles", 4000), - new Action("WriteRegistryValues", 5000), - new Action("RegisterProduct", 6100), - new Action("PublishComponents", 6200), - new Action("PublishFeatures", 6300), - new Action("PublishProduct", 6400), - new Action("InstallFinalize", 6600), - }; - } - } - - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs deleted file mode 100644 index f994dfef..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Windows.Forms; - using System.Globalization; - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.WindowsInstaller; - using View = WixToolset.Dtf.WindowsInstaller.View; - - [TestClass] - public class WindowsInstallerTest - { - public WindowsInstallerTest() - { - } - - [TestInitialize()] - public void Initialize() - { - } - - [TestCleanup()] - public void Cleanup() - { - } - - [TestMethod] - [Ignore] // Currently fails. - public void InstallerErrorMessages() - { - string msg3002 = Installer.GetErrorMessage(3002); - Console.WriteLine("3002=" + msg3002); - Assert.IsNotNull(msg3002); - Assert.IsTrue(msg3002.Length > 0); - } - - [TestMethod] - public void InstallerDatabaseSchema() - { - string dbFile = "InstallerDatabaseSchema.msi"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - db.Commit(); - } - - Assert.IsTrue(File.Exists(dbFile), "Checking whether created database file " + dbFile + " exists."); - - using (Database db = new Database(dbFile, DatabaseOpenMode.ReadOnly)) - { - TableCollection tables = db.Tables; - Assert.AreEqual(Schema.Tables.Count, tables.Count, "Counting tables."); - Assert.AreEqual(Schema.Property.Columns.Count, tables["Property"].Columns.Count, "Counting columns in Property table."); - - foreach (TableInfo tableInfo in tables) - { - Console.WriteLine(tableInfo.Name); - foreach (ColumnInfo columnInfo in tableInfo.Columns) - { - Console.WriteLine("\t{0} {1}", columnInfo.Name, columnInfo.ColumnDefinitionString); - } - } - } - } - - [TestMethod] - public void InstallerViewTables() - { - string dbFile = "InstallerViewTables.msi"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - db.Commit(); - - using (View view1 = db.OpenView("SELECT `Property`, `Value` FROM `Property` WHERE `Value` IS NOT NULL")) - { - IList viewTables = view1.Tables; - Assert.IsNotNull(viewTables); - Assert.AreEqual(1, viewTables.Count); - Assert.AreEqual("Property", viewTables[0].Name); - } - - using (View view2 = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES ('TestViewTables', 1)")) - { - IList viewTables = view2.Tables; - Assert.IsNotNull(viewTables); - Assert.AreEqual(1, viewTables.Count); - Assert.AreEqual("Property", viewTables[0].Name); - } - - using (View view3 = db.OpenView("UPDATE `Property` SET `Value` = 2 WHERE `Property` = 'TestViewTables'")) - { - IList viewTables = view3.Tables; - Assert.IsNotNull(viewTables); - Assert.AreEqual(1, viewTables.Count); - Assert.AreEqual("Property", viewTables[0].Name); - } - - using (View view4 = db.OpenView("alter table Property hold")) - { - IList viewTables = view4.Tables; - Assert.IsNotNull(viewTables); - Assert.AreEqual(1, viewTables.Count); - Assert.AreEqual("Property", viewTables[0].Name); - } - } - } - - [TestMethod] - public void InstallerInstallProduct() - { - string dbFile = "InstallerInstallProduct.msi"; - string productCode; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; - - db.Commit(); - } - - ProductInstallation installation = new ProductInstallation(productCode); - Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); - - Installer.SetInternalUI(InstallUIOptions.Silent); - ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); - - Exception caughtEx = null; - try - { - Installer.InstallProduct(dbFile, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); - - prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); - Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); - - Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine("==================================================================="); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - - ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); - - try - { - Installer.InstallProduct(dbFile, "REMOVE=All"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); - - Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed after removing."); - - prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); - Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); - } - - public static MessageResult ExternalUILogger( - InstallMessage messageType, - string message, - MessageButtons buttons, - MessageIcon icon, - MessageDefaultButton defaultButton) - { - Console.WriteLine("{0}: {1}", messageType, message); - return MessageResult.None; - } - - public static MessageResult ExternalUIRecordLogger( - InstallMessage messageType, - Record messageRecord, - MessageButtons buttons, - MessageIcon icon, - MessageDefaultButton defaultButton) - { - if (messageRecord != null) - { - if (messageRecord.FormatString.Length == 0 && messageRecord.FieldCount > 0) - { - messageRecord.FormatString = "1: [1] 2: [2] 3: [3] 4: [4] 5: [5]"; - } - Console.WriteLine("{0}: {1}", messageType, messageRecord.ToString()); - } - else - { - Console.WriteLine("{0}: (null)", messageType); - } - return MessageResult.None; - } - - [TestMethod] - [Ignore] // Currently fails. - public void InstallerMessageResources() - { - string message1101 = Installer.GetErrorMessage(1101); - Console.WriteLine("Message 1101: " + message1101); - Assert.IsNotNull(message1101); - Assert.IsTrue(message1101.Contains("file")); - - message1101 = Installer.GetErrorMessage(1101, CultureInfo.GetCultureInfo(1033)); - Console.WriteLine("Message 1101: " + message1101); - Assert.IsNotNull(message1101); - Assert.IsTrue(message1101.Contains("file")); - - string message2621 = Installer.GetErrorMessage(2621); - Console.WriteLine("Message 2621: " + message2621); - Assert.IsNotNull(message2621); - Assert.IsTrue(message2621.Contains("DLL")); - - string message3002 = Installer.GetErrorMessage(3002); - Console.WriteLine("Message 3002: " + message3002); - Assert.IsNotNull(message3002); - Assert.IsTrue(message3002.Contains("sequencing")); - } - - [TestMethod] - public void EnumComponentQualifiers() - { - foreach (ComponentInstallation comp in ComponentInstallation.AllComponents) - { - bool qualifiers = false; - foreach (ComponentInstallation.Qualifier qualifier in comp.Qualifiers) - { - if (!qualifiers) - { - Console.WriteLine(comp.Path); - qualifiers = true; - } - - Console.WriteLine("\t{0}: {1}", qualifier.QualifierCode, qualifier.Data); - } - } - } - - [TestMethod] - public void DeleteRecord() - { - string dbFile = "DeleteRecord.msi"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - string query = "SELECT `Property`, `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'"; - - using (View view = db.OpenView(query)) - { - view.Execute(); - - Record rec = view.Fetch(); - - Console.WriteLine("Calling ToString() : " + rec); - - view.Delete(rec); - } - - Assert.AreEqual(0, db.ExecuteStringQuery(query).Count); - } - } - - [TestMethod] - public void InsertRecordThenTryFormatString() - { - string dbFile = "InsertRecordThenTryFormatString.msi"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - string parameterFormatString = "[1]"; - string[] properties = new string[] - { - "SonGoku", "Over 9000", - }; - - string query = "SELECT `Property`, `Value` FROM `Property`"; - - using (View view = db.OpenView(query)) - { - using (Record rec = new Record(2)) - { - rec[1] = properties[0]; - rec[2] = properties[1]; - rec.FormatString = parameterFormatString; - Console.WriteLine("Format String before inserting: " + rec.FormatString); - view.Insert(rec); - - Console.WriteLine("Format String after inserting: " + rec.FormatString); - // After inserting, the format string is invalid. - Assert.AreEqual(String.Empty, rec.ToString()); - - // Setting the format string manually makes it valid again. - rec.FormatString = parameterFormatString; - Assert.AreEqual(properties[0], rec.ToString()); - } - } - } - } - - [TestMethod] - public void SeekRecordThenTryFormatString() - { - string dbFile = "SeekRecordThenTryFormatString.msi"; - - using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db); - WindowsInstallerUtils.CreateTestProduct(db); - - string parameterFormatString = "[1]"; - string[] properties = new string[] - { - "SonGoku", "Over 9000", - }; - - string query = "SELECT `Property`, `Value` FROM `Property`"; - - using (View view = db.OpenView(query)) - { - using (Record rec = new Record(2)) - { - rec[1] = properties[0]; - rec[2] = properties[1]; - rec.FormatString = parameterFormatString; - Console.WriteLine("Record fields before seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); - view.Seek(rec); - - //TODO: Why does view.Seek remove the record fields? - Console.WriteLine("Record fields after seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); - // After inserting, the format string is invalid. - Assert.AreEqual(String.Empty, rec.ToString()); - } - } - } - } - - [TestMethod] - public void TestToString() - { - string defaultString = "1: "; - string vegetaShout = "It's OVER 9000!!"; - string gokuPowerLevel = "9001"; - string nappaInquiry = "Vegeta, what's the Scouter say about his power level?"; - string parameterFormatString = "[1]"; - - Record rec = new Record(1); - Assert.AreEqual(defaultString, rec.ToString(), "Testing default FormatString"); - - rec.FormatString = String.Empty; - Assert.AreEqual(defaultString, rec.ToString(), "Explicitly set the FormatString to the empty string."); - - rec.FormatString = vegetaShout; - Assert.AreEqual(vegetaShout, rec.ToString(), "Testing text only (empty FormatString)"); - - rec.FormatString = gokuPowerLevel; - Assert.AreEqual(gokuPowerLevel, rec.ToString(), "Testing numbers only from a record that wasn't fetched."); - - Record rec2 = new Record(nappaInquiry); - rec2.FormatString = parameterFormatString; - Assert.AreEqual(nappaInquiry, rec2.ToString(), "Testing text with a FormatString set."); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs deleted file mode 100644 index 3bdf5acd..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.IO; - using System.Windows.Forms; - using System.Globalization; - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using WixToolset.Dtf.WindowsInstaller; - using View = WixToolset.Dtf.WindowsInstaller.View; - - [TestClass] - public class WindowsInstallerTransactions - { - [TestInitialize()] - public void Initialize() - { - } - - [TestCleanup()] - public void Cleanup() - { - } - - [TestMethod] - [Ignore] // Requires elevation. - public void InstallerTransactTwoProducts() - { - string dbFile1 = "InstallerTransactProduct1.msi"; - string dbFile2 = "InstallerTransactProduct2.msi"; - string productCode1; - string productCode2; - - using (Database db1 = new Database(dbFile1, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db1); - WindowsInstallerUtils.CreateTestProduct(db1); - - productCode1 = db1.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; - - db1.Commit(); - } - - using (Database db2 = new Database(dbFile2, DatabaseOpenMode.CreateDirect)) - { - WindowsInstallerUtils.InitializeProductDatabase(db2); - WindowsInstallerUtils.CreateTestProduct(db2); - - productCode2 = db2.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; - - db2.Commit(); - } - - ProductInstallation installation1 = new ProductInstallation(productCode1); - ProductInstallation installation2 = new ProductInstallation(productCode2); - Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed before starting."); - Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed before starting."); - - Installer.SetInternalUI(InstallUIOptions.Silent); - ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); - - Transaction transaction = new Transaction("TestInstallTransaction", TransactionAttributes.None); - - Exception caughtEx = null; - try - { - Installer.InstallProduct(dbFile1, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while installing product 1: " + caughtEx); - - Console.WriteLine(); - Console.WriteLine("==================================================================="); - Console.WriteLine(); - - try - { - Installer.InstallProduct(dbFile2, String.Empty); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while installing product 2: " + caughtEx); - - transaction.Commit(); - transaction.Close(); - - prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); - Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); - - Assert.IsTrue(installation1.IsInstalled, "Checking that product 1 is installed."); - Assert.IsTrue(installation2.IsInstalled, "Checking that product 2 is installed."); - - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine("==================================================================="); - Console.WriteLine("==================================================================="); - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - - ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, - InstallLogModes.FatalExit | - InstallLogModes.Error | - InstallLogModes.Warning | - InstallLogModes.User | - InstallLogModes.Info | - InstallLogModes.ResolveSource | - InstallLogModes.OutOfDiskSpace | - InstallLogModes.ActionStart | - InstallLogModes.ActionData | - InstallLogModes.CommonData | - InstallLogModes.Progress | - InstallLogModes.Initialize | - InstallLogModes.Terminate | - InstallLogModes.ShowDialog); - Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); - - transaction = new Transaction("TestUninstallTransaction", TransactionAttributes.None); - - try - { - Installer.InstallProduct(dbFile1, "REMOVE=All"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while removing product 1: " + caughtEx); - - try - { - Installer.InstallProduct(dbFile2, "REMOVE=All"); - } - catch (Exception ex) { caughtEx = ex; } - Assert.IsNull(caughtEx, "Exception thrown while removing product 2: " + caughtEx); - - transaction.Commit(); - transaction.Close(); - - Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed after removing."); - Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed after removing."); - - prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); - Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs deleted file mode 100644 index 644f1988..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Test -{ - using System; - using System.Collections.Generic; - using System.Text; - using WixToolset.Dtf.WindowsInstaller; - - public class WindowsInstallerUtils - { - public static void InitializeProductDatabase(Database db) - { - InitializeProductDatabase(db, false); - } - - public static void InitializeProductDatabase(Database db, bool sixtyFourBit) - { - db.SummaryInfo.CodePage = (short) Encoding.Default.CodePage; - db.SummaryInfo.Title = "Windows Installer Test"; - db.SummaryInfo.Subject = db.SummaryInfo.Title; - db.SummaryInfo.Author = typeof(WindowsInstallerUtils).Assembly.FullName; - db.SummaryInfo.CreatingApp = db.SummaryInfo.Author; - db.SummaryInfo.Comments = typeof(WindowsInstallerUtils).FullName + ".CreateBasicDatabase()"; - db.SummaryInfo.Keywords = "Installer,MSI,Database"; - db.SummaryInfo.PageCount = 300; - db.SummaryInfo.WordCount = 0; - db.SummaryInfo.RevisionNumber = Guid.NewGuid().ToString("B").ToUpper(); - db.SummaryInfo.Template = (sixtyFourBit ? "x64" : "Intel") + ";0"; - - foreach (TableInfo tableInfo in Schema.Tables) - { - db.Execute(tableInfo.SqlCreateString); - } - - db.Execute("INSERT INTO `Directory` (`Directory`, `DefaultDir`) VALUES ('TARGETDIR', 'SourceDir')"); - db.Execute("INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES ('ProgramFilesFolder', 'TARGETDIR', '.')"); - - foreach (Action action in Sequence.InstallExecute) - { - db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('{0}', {1})", - action.Name, action.Sequence); - } - } - - public const string UpgradeCode = "{05955FE8-005F-4695-A81F-D559338065BB}"; - - public static void CreateTestProduct(Database db) - { - Guid productGuid = Guid.NewGuid(); - - string[] properties = new string[] - { - "ProductCode", productGuid.ToString("B").ToUpper(), - "UpgradeCode", UpgradeCode, - "ProductName", "Windows Installer Test Product " + productGuid.ToString("P").ToUpper(), - "ProductVersion", "1.0.0.0000", - }; - - using (View view = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)")) - { - using (Record rec = new Record(2)) - { - for (int i = 0; i < properties.Length; i += 2) - { - rec[1] = properties[i]; - rec[2] = properties[i + 1]; - view.Execute(rec); - } - } - } - - int randomId = new Random().Next(10000); - string productDir = "TestDir" + randomId; - db.Execute( - "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) " + - "VALUES ('TestDir', 'ProgramFilesFolder', 'TestDir|{0}:.')", productDir); - - string compId = Guid.NewGuid().ToString("B").ToUpper(); - db.Execute( - "INSERT INTO `Component` " + - "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + - "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", - "TestRegComp1", - compId, - "TestDir", - (int) ComponentAttributes.RegistryKeyPath, - "TestReg1"); - - string productReg = "TestReg" + randomId; - db.Execute( - "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}')", - "TestReg1", - -1, - @"Software\Microsoft\Windows Installer Test\" + productReg, - "TestRegComp1"); - - db.Execute( - "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", - "TestFeature1", - "Test Feature 1", - 1, - (int) FeatureAttributes.None); - - db.Execute( - "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", - "TestFeature1", - "TestRegComp1"); - } - - public static void AddFeature(Database db, string featureName) - { - db.Execute( - "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", - featureName, - featureName, - 1, - (int) FeatureAttributes.None); - } - - public static void AddRegistryComponent(Database db, - string featureName, string compName, string compId, - string keyName, string keyValueName, string value) - { - db.Execute( - "INSERT INTO `Component` " + - "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + - "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", - compName, - compId, - "TestDir", - (int) ComponentAttributes.RegistryKeyPath, - compName + "Reg1"); - db.Execute( - "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}', '{4}', '{5}')", - compName + "Reg1", - -1, - @"Software\Microsoft\Windows Installer Test\" + keyName, - keyValueName, - value, - compName); - db.Execute( - "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", - featureName, - compName); - } - - public static void AddFileComponent(Database db, - string featureName, string compName, string compId, - string fileKey, string fileName) - { - db.Execute( - "INSERT INTO `Component` " + - "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + - "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", - compName, - compId, - "TestDir", - (int) ComponentAttributes.None, - fileKey); - db.Execute( - "INSERT INTO `File` " + - "(`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) " + - "VALUES ('{0}', '{1}', '{2}', 1, 0, 1)", - fileKey, - compName, - fileName); - db.Execute( - "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", - featureName, - compName); - } - } -} diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj b/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj deleted file mode 100644 index 0d2a50fb..00000000 --- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - - {16F5202F-9276-4166-975C-C9654BAF8012} - Library - WixToolsetTests.Dtf - WixToolsetTests.Dtf.WindowsInstaller - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - false - false - v4.7.2 - - - - - - - - - - - - - - - - - - - - {85225597-5121-4361-8332-4E3246D5BBF5} - WixToolset.Dtf.WindowsInstaller - - - - - - 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 @@ @echo Building dtf %_C% +msbuild -Restore SfxCA\sfxca_t.proj -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\dtf_sfxca.binlog || exit /b + msbuild -Restore -t:Pack dtf.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\dtf_build.binlog || exit /b @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 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression", "WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression", "WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Cab", "WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression.Cab", "WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Zip", "WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression.Zip", "WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Resources", "WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Resources", "WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller", "WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller", "WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Linq", "WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller.Linq", "WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Package", "WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller.Package", "WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "test\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "test\WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "test\WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "test\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "test\WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "test\WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{222DA0A6-5E28-4D7A-A227-B818B0C55BAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.MakeSfxCA", "WixToolset.Dtf.MakeSfxCA\WixToolset.Dtf.MakeSfxCA.csproj", "{F8CA8E72-08BF-4A8A-AD32-C638616B72E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.CustomAction", "WixToolset.Dtf.CustomAction\WixToolset.Dtf.CustomAction.csproj", "{D6C0D94C-80A5-495C-B573-C7440A8594F5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -129,8 +135,8 @@ Global {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.Build.0 = Debug|Any CPU {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.ActiveCfg = Debug|Any CPU {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.Build.0 = Debug|Any CPU - {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Release|Any CPU + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Debug|Any CPU {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.ActiveCfg = Release|Any CPU {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.Build.0 = Release|Any CPU {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.ActiveCfg = Release|Any CPU @@ -141,8 +147,8 @@ Global {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.Build.0 = Debug|Any CPU {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.ActiveCfg = Debug|Any CPU {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.Build.0 = Debug|Any CPU - {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Release|Any CPU + {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Debug|Any CPU {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.ActiveCfg = Release|Any CPU {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.Build.0 = Release|Any CPU {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.ActiveCfg = Release|Any CPU @@ -153,8 +159,8 @@ Global {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.Build.0 = Debug|Any CPU {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.ActiveCfg = Debug|Any CPU {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.Build.0 = Debug|Any CPU - {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Release|Any CPU + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Debug|Any CPU {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.ActiveCfg = Release|Any CPU {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.Build.0 = Release|Any CPU {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.ActiveCfg = Release|Any CPU @@ -165,8 +171,8 @@ Global {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.Build.0 = Debug|Any CPU {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.ActiveCfg = Debug|Any CPU {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.Build.0 = Debug|Any CPU - {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Release|Any CPU + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Debug|Any CPU {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.ActiveCfg = Release|Any CPU {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.Build.0 = Release|Any CPU {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.ActiveCfg = Release|Any CPU @@ -177,8 +183,8 @@ Global {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.Build.0 = Debug|Any CPU {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.ActiveCfg = Debug|Any CPU {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.Build.0 = Debug|Any CPU - {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Release|Any CPU + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Debug|Any CPU {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.ActiveCfg = Release|Any CPU {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.Build.0 = Release|Any CPU {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.ActiveCfg = Release|Any CPU @@ -189,29 +195,47 @@ Global {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.Build.0 = Debug|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.ActiveCfg = Debug|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.Build.0 = Debug|Any CPU - {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Release|Any CPU + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Debug|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.ActiveCfg = Release|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.Build.0 = Release|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.ActiveCfg = Release|Any CPU {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.Build.0 = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.ActiveCfg = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.Build.0 = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.ActiveCfg = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.Build.0 = Debug|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.Build.0 = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.ActiveCfg = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.Build.0 = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.ActiveCfg = Release|Any CPU - {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.Build.0 = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x64.Build.0 = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x86.Build.0 = Debug|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|Any CPU.Build.0 = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x64.ActiveCfg = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x64.Build.0 = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x86.ActiveCfg = Release|Any CPU + {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x86.Build.0 = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x64.ActiveCfg = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x64.Build.0 = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x86.ActiveCfg = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x86.Build.0 = Debug|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|Any CPU.Build.0 = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x64.ActiveCfg = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x64.Build.0 = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x86.ActiveCfg = Release|Any CPU + {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} + {4544158C-2D63-4146-85FF-62169280144E} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} + {328799BB-7B03-4B28-8180-4132211FD07D} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} + {16F5202F-9276-4166-975C-C9654BAF8012} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} + {137D376B-989F-4FEA-9A67-01D8D38CA0DE} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} + {4F55F9B8-D8B6-41EB-8796-221B4CD98324} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB57C98D-C0C2-4805-AED3-C19B47759DBD} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs new file mode 100644 index 00000000..981ecc69 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs @@ -0,0 +1,1165 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Collections.Generic; + using System.Runtime.Serialization; + using System.Runtime.Serialization.Formatters.Binary; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.Compression; + using WixToolset.Dtf.Compression.Cab; + + [TestClass] + public class CabTest + { + public CabTest() + { + } + + [TestInitialize] + public void Initialize() + { + } + + [TestCleanup] + public void Cleanup() + { + } + + [TestMethod] + public void CabinetMultithread() + { + this.multithreadExceptions = new List(); + + const int threadCount = 10; + IList threads = new List(threadCount); + + for (int i = 0; i < threadCount; i++) + { + Thread thread = new Thread(new ThreadStart(this.CabinetMultithreadWorker)); + thread.Name = "CabinetMultithreadWorker_" + i; + threads.Add(thread); + } + + foreach (Thread thread in threads) + { + thread.Start(); + } + + foreach (Thread thread in threads) + { + thread.Join(); + } + + foreach (Exception ex in this.multithreadExceptions) + { + Console.WriteLine(); + Console.WriteLine(ex); + } + Assert.AreEqual(0, this.multithreadExceptions.Count); + } + + private IList multithreadExceptions; + + private void CabinetMultithreadWorker() + { + try + { + string threadName = Thread.CurrentThread.Name; + int threadNumber = Int32.Parse(threadName.Substring(threadName.IndexOf('_') + 1)); + this.RunCabinetPackUnpack(100, 10240 + threadNumber, 0, 0, CompressionLevel.Normal); + } + catch (Exception ex) + { + this.multithreadExceptions.Add(ex); + } + } + + [TestMethod] + public void CabinetFileCounts() + { + this.RunCabinetPackUnpack(0, 10, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(100, 10, 0, 0, CompressionLevel.Normal); + } + + [TestMethod] + [Ignore] // Takes ~5 minutes and 66000 is over the 65535 limit anyway. + public void CabinetExtremeFileCounts() + { + this.RunCabinetPackUnpack(66000, 10); + } + + [TestMethod] + public void CabinetFileSizes() + { + this.RunCabinetPackUnpack(1, 0, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 1, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 2, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 3, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 4, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 5, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 6, 0, 0, CompressionLevel.Normal); + // Skip file sizes 7-9: see "buggy" file sizes test below. + this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 11, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 12, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 100 * 1024, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 10 * 1024 * 1024, 0, 0, CompressionLevel.Normal); + } + + [TestMethod] + public void CabinetBuggyFileSizes() + { + // Windows' cabinet.dll has a known bug (#55001 in Windows OS Bugs) + // LZX compression causes an AV with file sizes of 7, 8, or 9 bytes. + try + { + this.RunCabinetPackUnpack(1, 7, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 8, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 9, 0, 0, CompressionLevel.Normal); + } + catch (AccessViolationException) + { + Assert.Fail("Known 7,8,9 file size bug detected in Windows' cabinet.dll."); + } + } + + [Timeout(36000000), TestMethod] + [Ignore] // Takes too long to run regularly. + public void CabinetExtremeFileSizes() + { + this.RunCabinetPackUnpack(10, 512L * 1024 * 1024); // 5GB + //this.RunCabinetPackUnpack(1, 5L * 1024 * 1024 * 1024); // 5GB + } + + [TestMethod] + public void CabinetFolders() + { + this.RunCabinetPackUnpack(0, 10, 1, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(1, 10, 1, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(100, 10, 1, 0, CompressionLevel.Normal); + + IList fileInfo; + fileInfo = this.RunCabinetPackUnpack(7, 100 * 1024, 250 * 1024, 0, CompressionLevel.None); + Assert.AreEqual(2, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, + "Testing whether cabinet has the correct # of folders."); + + fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 250 * 1024, 0, CompressionLevel.None); + Assert.AreEqual(3, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, + "Testing whether cabinet has the correct # of folders."); + + fileInfo = this.RunCabinetPackUnpack(2, 100 * 1024, 40 * 1024, 0, CompressionLevel.None); + Assert.AreEqual(1, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber, + "Testing whether cabinet has the correct # of folders."); + } + + [TestMethod] + public void CabinetArchiveCounts() + { + IList fileInfo; + fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 0, 400 * 1024, CompressionLevel.None); + Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, + "Testing whether archive spans the correct # of cab files."); + + fileInfo = this.RunCabinetPackUnpack(2, 90 * 1024, 0, 40 * 1024, CompressionLevel.None); + Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, + "Testing whether archive spans the correct # of cab files."); + } + + [TestMethod] + public void CabinetProgress() + { + CompressionTestUtil.ExpectedProgress = new List(new int[][] { + // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartArchive, 7, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 7, 15, 3, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartArchive, 13, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 13, 15, 6, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartArchive, 14, 15, 7, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 }, + // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 3, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 3, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 6, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 6, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 }, + }); + + try + { + this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024, CompressionLevel.None); + } + finally + { + CompressionTestUtil.ExpectedProgress = null; + } + } + + [TestMethod] + public void CabArchiveSizeParam() + { + Console.WriteLine("Testing various values for the maxArchiveSize parameter."); + this.RunCabinetPackUnpack(5, 1024, 0, Int64.MinValue); + this.RunCabinetPackUnpack(5, 1024, 0, -1); + this.RunCabinetPackUnpack(5, 10, 0, 2); + this.RunCabinetPackUnpack(5, 100, 0, 256); + this.RunCabinetPackUnpack(5, 24000, 0, 32768); + this.RunCabinetPackUnpack(5, 1024, 0, Int64.MaxValue); + } + + [TestMethod] + public void CabFolderSizeParam() + { + Console.WriteLine("Testing various values for the maxFolderSize parameter."); + this.RunCabinetPackUnpack(5, 10, Int64.MinValue, 0); + this.RunCabinetPackUnpack(5, 10, -1, 0); + this.RunCabinetPackUnpack(5, 10, 2, 0); + this.RunCabinetPackUnpack(5, 10, 16, 0); + this.RunCabinetPackUnpack(5, 10, 100, 0); + this.RunCabinetPackUnpack(5, 10, Int64.MaxValue, 0); + } + + [TestMethod] + public void CabCompLevelParam() + { + Console.WriteLine("Testing various values for the compressionLevel parameter."); + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.None); + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Min); + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Normal); + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Max); + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.None - 1)); + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1)); + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MinValue); + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MaxValue); + } + + [TestMethod] + public void CabEngineNullParams() + { + string[] testFiles = new string[] { "test.txt" }; + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.cab", null, null); + + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.CompressionLevel = CompressionLevel.None; + + CompressionTestUtil.TestCompressionEngineNullParams( + cabEngine, streamContext, testFiles); + } + } + + [TestMethod] + public void CabBadPackStreamContexts() + { + string[] testFiles = new string[] { "test.txt" }; + CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000); + + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.CompressionLevel = CompressionLevel.None; + + CompressionTestUtil.TestBadPackStreamContexts(cabEngine, "test.cab", testFiles); + } + } + + [TestMethod] + public void CabEngineNoTempFileTest() + { + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("testnotemp.txt", 0, txtSize); + + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("testnotemp.cab", null, null); + + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.UseTempFiles = false; + cabEngine.Pack(streamContext, new string[] { "testnotemp.txt" }); + } + + new CabInfo("testnotemp.cab").UnpackFile("testnotemp.txt", "testnotemp2.txt"); + Assert.AreEqual(txtSize, new FileInfo("testnotemp2.txt").Length); + } + + [TestMethod] + public void CabExtractorIsCabinet() + { + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); + new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null); + using (CabEngine cabEngine = new CabEngine()) + { + bool isCab; + using (Stream fileStream = File.OpenRead("test.txt")) + { + isCab = cabEngine.IsArchive(fileStream); + } + Assert.IsFalse(isCab); + using (Stream cabStream = File.OpenRead("test.cab")) + { + isCab = cabEngine.IsArchive(cabStream); + } + Assert.IsTrue(isCab); + using (Stream cabStream = File.OpenRead("test.cab")) + { + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) + { + fileStream.Seek(0, SeekOrigin.End); + byte[] buf = new byte[1024]; + int count; + while ((count = cabStream.Read(buf, 0, buf.Length)) > 0) + { + fileStream.Write(buf, 0, count); + } + fileStream.Seek(0, SeekOrigin.Begin); + isCab = cabEngine.IsArchive(fileStream); + } + } + Assert.IsFalse(isCab); + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) + { + fileStream.Write(new byte[] { (byte) 'M', (byte) 'S', (byte) 'C', (byte) 'F' }, 0, 4); + fileStream.Seek(0, SeekOrigin.Begin); + isCab = cabEngine.IsArchive(fileStream); + } + Assert.IsFalse(isCab); + } + } + + [TestMethod] + public void CabExtractorFindOffset() + { + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); + new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null); + using (CabEngine cabEngine = new CabEngine()) + { + long offset; + using (Stream fileStream = File.OpenRead("test.txt")) + { + offset = cabEngine.FindArchiveOffset(fileStream); + } + Assert.AreEqual(-1, offset); + using (Stream cabStream = File.OpenRead("test.cab")) + { + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite)) + { + fileStream.Seek(0, SeekOrigin.End); + byte[] buf = new byte[1024]; + int count; + while ((count = cabStream.Read(buf, 0, buf.Length)) > 0) + { + fileStream.Write(buf, 0, count); + } + fileStream.Seek(0, SeekOrigin.Begin); + offset = cabEngine.FindArchiveOffset(fileStream); + } + } + Assert.AreEqual(txtSize, offset); + } + } + + [TestMethod] + public void CabExtractorGetFiles() + { + IList fileInfo; + CabInfo cabInfo = new CabInfo("testgetfiles.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("testgetfiles0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("testgetfiles1.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "testgetfiles0.txt", "testgetfiles1.txt" }, null); + using (CabEngine cabEngine = new CabEngine()) + { + IList files; + using (Stream cabStream = File.OpenRead("testgetfiles.cab")) + { + files = cabEngine.GetFiles(cabStream); + } + Assert.IsNotNull(files); + Assert.AreEqual(2, files.Count); + Assert.AreEqual("testgetfiles0.txt", files[0]); + Assert.AreEqual("testgetfiles1.txt", files[1]); + + using (Stream cabStream = File.OpenRead("testgetfiles.cab")) + { + files = cabEngine.GetFiles(new ArchiveFileStreamContext("testgetfiles.cab"), null); + } + Assert.IsNotNull(files); + Assert.AreEqual(2, files.Count); + Assert.AreEqual("testgetfiles0.txt", files[0]); + Assert.AreEqual("testgetfiles1.txt", files[1]); + + using (Stream cabStream = File.OpenRead("testgetfiles.cab")) + { + fileInfo = cabEngine.GetFileInfo(cabStream); + } + Assert.IsNotNull(fileInfo); + Assert.AreEqual(2, fileInfo.Count); + Assert.AreEqual("testgetfiles0.txt", fileInfo[0].Name); + Assert.AreEqual("testgetfiles1.txt", fileInfo[1].Name); + using (Stream cabStream = File.OpenRead("testgetfiles.cab")) + { + fileInfo = cabEngine.GetFileInfo(new ArchiveFileStreamContext("testgetfiles.cab"), null); + } + Assert.IsNotNull(fileInfo); + Assert.AreEqual(2, fileInfo.Count); + Assert.AreEqual("testgetfiles0.txt", fileInfo[0].Name); + Assert.AreEqual("testgetfiles1.txt", fileInfo[1].Name); + } + + fileInfo = this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024); + Assert.IsNotNull(fileInfo); + Assert.AreEqual(15, fileInfo.Count); + for (int i = 0; i < fileInfo.Count; i++) + { + Assert.IsNull(fileInfo[i].Archive); + Assert.AreEqual(TEST_FILENAME_PREFIX + i + ".txt", fileInfo[i].Name); + Assert.IsTrue(DateTime.Now - fileInfo[i].LastWriteTime < new TimeSpan(0, 1, 0)); + } + } + + [TestMethod] + public void CabExtractorExtract() + { + int txtSize = 40960; + CabInfo cabInfo = new CabInfo("test.cab"); + CompressionTestUtil.GenerateRandomFile("test0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test1.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "test0.txt", "test1.txt" }, null); + using (CabEngine cabEngine = new CabEngine()) + { + using (Stream cabStream = File.OpenRead("test.cab")) + { + using (Stream exStream = cabEngine.Unpack(cabStream, "test0.txt")) + { + string str = new StreamReader(exStream).ReadToEnd(); + string expected = new StreamReader("test0.txt").ReadToEnd(); + Assert.AreEqual(expected, str); + } + cabStream.Seek(0, SeekOrigin.Begin); + using (Stream exStream = cabEngine.Unpack(cabStream, "test1.txt")) + { + string str = new StreamReader(exStream).ReadToEnd(); + string expected = new StreamReader("test1.txt").ReadToEnd(); + Assert.AreEqual(expected, str); + } + } + using (Stream txtStream = File.OpenRead("test0.txt")) + { + Exception caughtEx = null; + try + { + cabEngine.Unpack(txtStream, "test0.txt"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(CabException)); + Assert.AreEqual(2, ((CabException) caughtEx).Error); + Assert.AreEqual(0, ((CabException) caughtEx).ErrorCode); + Assert.AreEqual("Cabinet file does not have the correct format.", caughtEx.Message); + } + } + } + + [TestMethod] + public void CabBadUnpackStreamContexts() + { + int txtSize = 40960; + CabInfo cabInfo = new CabInfo("test2.cab"); + CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null); + + using (CabEngine cabEngine = new CabEngine()) + { + CompressionTestUtil.TestBadUnpackStreamContexts(cabEngine, "test2.cab"); + } + } + + [TestMethod] + public void CabinetExtractUpdate() + { + int fileCount = 5, fileSize = 2048; + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = "€" + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + + CabInfo cabInfo = new CabInfo("testupdate.cab"); + cabInfo.Pack(dirA); + cabInfo.Unpack(dirB); + + DateTime originalTime = File.GetLastWriteTime(Path.Combine(dirA, "€1.txt")); + DateTime pastTime = originalTime - new TimeSpan(0, 5, 0); + DateTime futureTime = originalTime + new TimeSpan(0, 5, 0); + + using (CabEngine cabEngine = new CabEngine()) + { + string cabName = "testupdate.cab"; + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(cabName, dirB, null); + streamContext.ExtractOnlyNewerFiles = true; + + Assert.AreEqual(true, streamContext.ExtractOnlyNewerFiles); + Assert.IsNotNull(streamContext.ArchiveFiles); + Assert.AreEqual(1, streamContext.ArchiveFiles.Count); + Assert.AreEqual(cabName, streamContext.ArchiveFiles[0]); + Assert.AreEqual(dirB, streamContext.Directory); + + File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), futureTime); + cabEngine.Unpack(streamContext, null); + Assert.IsTrue(File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime > new TimeSpan(0, 4, 55)); + + File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), pastTime); + File.SetLastWriteTime(Path.Combine(dirB, "€2.txt"), pastTime); + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.ReadOnly); + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.Hidden); + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.System); + + cabEngine.Unpack(streamContext, null); + Assert.IsTrue((File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime).Duration() < new TimeSpan(0, 0, 5)); + + // Just test the rest of the streamContext properties here. + IDictionary testMap = new Dictionary(); + streamContext = new ArchiveFileStreamContext(cabName, dirB, testMap); + Assert.AreSame(testMap, streamContext.Files); + + Assert.IsFalse(streamContext.EnableOffsetOpen); + streamContext.EnableOffsetOpen = true; + Assert.IsTrue(streamContext.EnableOffsetOpen); + streamContext = new ArchiveFileStreamContext(cabName, ".", testMap); + Assert.AreEqual(".", streamContext.Directory); + string[] testArchiveFiles = new string[] { cabName }; + streamContext = new ArchiveFileStreamContext(testArchiveFiles, ".", testMap); + Assert.AreSame(testArchiveFiles, streamContext.ArchiveFiles); + } + } + + [TestMethod] + public void CabinetOffset() + { + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("base.txt", 1, 2 * txtSize + 4); + + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("base.txt", null, null); + streamContext.EnableOffsetOpen = true; + + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.Pack(streamContext, new string[] { "test.txt" }); + } + + Assert.IsTrue(new FileInfo("base.txt").Length > 2 * txtSize + 4); + + string saveText; + using (Stream txtStream = File.OpenRead("test.txt")) + { + saveText = new StreamReader(txtStream).ReadToEnd(); + } + File.Delete("test.txt"); + + using (CabEngine cex = new CabEngine()) + { + cex.Unpack(streamContext, null); + } + string testText; + using (Stream txtStream = File.OpenRead("test.txt")) + { + testText = new StreamReader(txtStream).ReadToEnd(); + } + Assert.AreEqual(saveText, testText); + } + + [TestMethod] + public void CabinetUtfPaths() + { + string[] files = new string[] + { + "어그리먼트送信ポート1ßà_Agreement.txt", + "콘토소ßà_MyProfile.txt", + "파트너1ßà_PartnerProfile.txt", + }; + + string dirA = "utf8-A"; + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = "utf8-B"; + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + int txtSize = 1024; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[0]), 0, txtSize); + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[1]), 1, txtSize); + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[2]), 2, txtSize); + + ArchiveFileStreamContext streamContextA = new ArchiveFileStreamContext("utf8.cab", dirA, null); + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.Pack(streamContextA, files); + } + + ArchiveFileStreamContext streamContextB = new ArchiveFileStreamContext("utf8.cab", dirB, null); + using (CabEngine cex = new CabEngine()) + { + cex.Unpack(streamContextB, null); + } + + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + } + + [TestMethod] + //[Ignore] // Requires clean environment. + public void CabInfoProperties() + { + Exception caughtEx; + CabInfo cabInfo = new CabInfo("test.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); + + Assert.AreEqual(new FileInfo("test.cab").Directory.FullName, cabInfo.Directory.FullName, "CabInfo.FullName"); + Assert.AreEqual(new FileInfo("test.cab").DirectoryName, cabInfo.DirectoryName, "CabInfo.DirectoryName"); + Assert.AreEqual(new FileInfo("test.cab").Length, cabInfo.Length, "CabInfo.Length"); + Assert.AreEqual("test.cab", cabInfo.Name, "CabInfo.Name"); + Assert.AreEqual(new FileInfo("test.cab").FullName, cabInfo.ToString(), "CabInfo.ToString()"); + cabInfo.CopyTo("test3.cab"); + caughtEx = null; + try + { + cabInfo.CopyTo("test3.cab"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(IOException), "CabInfo.CopyTo() caught exception: " + caughtEx); + cabInfo.CopyTo("test3.cab", true); + cabInfo.MoveTo("test4.cab"); + Assert.AreEqual("test4.cab", cabInfo.Name); + Assert.IsTrue(cabInfo.Exists, "CabInfo.Exists()"); + Assert.IsTrue(cabInfo.IsValid(), "CabInfo.IsValid"); + cabInfo.Delete(); + Assert.IsFalse(cabInfo.Exists, "!CabInfo.Exists()"); + } + + [TestMethod] + //[Ignore] // Requires clean environment. + public void CabInfoNullParams() + { + int fileCount = 10, fileSize = 1024; + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = "cabinfo-" + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + + CabInfo cabInfo = new CabInfo("testnull.cab"); + + CompressionTestUtil.TestArchiveInfoNullParams(cabInfo, dirA, dirB, files); + } + + [TestMethod] + public void CabInfoGetFiles() + { + IList fileInfo; + CabInfo cabInfo = new CabInfo("test.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt" }, null); + + fileInfo = cabInfo.GetFiles(); + Assert.IsNotNull(fileInfo); + Assert.AreEqual(2, fileInfo.Count); + Assert.AreEqual("testinfo0.txt", fileInfo[0].Name); + Assert.AreEqual("testinfo1.txt", fileInfo[1].Name); + + fileInfo = cabInfo.GetFiles("*.txt"); + Assert.IsNotNull(fileInfo); + Assert.AreEqual(2, fileInfo.Count); + Assert.AreEqual("testinfo0.txt", fileInfo[0].Name); + Assert.AreEqual("testinfo1.txt", fileInfo[1].Name); + + fileInfo = cabInfo.GetFiles("testinfo1.txt"); + Assert.IsNotNull(fileInfo); + Assert.AreEqual(1, fileInfo.Count); + Assert.AreEqual("testinfo1.txt", fileInfo[0].Name); + } + + [TestMethod] + public void CabInfoCompressExtract() + { + int fileCount = 10, fileSize = 1024; + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + Directory.CreateDirectory(Path.Combine(dirA, "sub")); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = "€" + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + CompressionTestUtil.GenerateRandomFile(Path.Combine(Path.Combine(dirA, "sub"), "€-.txt"), fileCount + 1, fileSize); + + CabInfo cabInfo = new CabInfo("test.cab"); + cabInfo.Pack(dirA); + cabInfo.Unpack(dirB); + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsFalse(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + cabInfo.Pack(dirA, true, CompressionLevel.Normal, null); + cabInfo.Unpack(dirB); + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + Directory.Delete(dirB, true); + Directory.Delete(Path.Combine(dirA, "sub"), true); + Directory.CreateDirectory(dirB); + cabInfo.Delete(); + + cabInfo.PackFiles(dirA, files, null); + cabInfo.UnpackFiles(files, dirB, null); + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + cabInfo.Delete(); + + IDictionary testMap = new Dictionary(files.Length); + for (int iFile = 0; iFile < fileCount; iFile++) + { + testMap[files[iFile] + ".key"] = files[iFile]; + } + cabInfo.PackFileSet(dirA, testMap); + cabInfo.UnpackFileSet(testMap, dirB); + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + testMap.Remove(files[1] + ".key"); + cabInfo.UnpackFileSet(testMap, dirB); + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsFalse(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + cabInfo.Delete(); + + cabInfo.PackFiles(dirA, files, null); + cabInfo.UnpackFile("€2.txt", Path.Combine(dirB, "test.txt")); + Assert.IsTrue(File.Exists(Path.Combine(dirB, "test.txt"))); + Assert.AreEqual(1, Directory.GetFiles(dirB).Length); + } + + [TestMethod] + //[Ignore] // Requires clean environment. + public void CabFileInfoProperties() + { + CabInfo cabInfo = new CabInfo("test.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); + File.SetAttributes("test01.txt", FileAttributes.ReadOnly | FileAttributes.Archive); + DateTime testTime = File.GetLastWriteTime("test01.txt"); + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); + File.SetAttributes("test01.txt", FileAttributes.Archive); + + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); + Assert.AreEqual(cabInfo.FullName, cfi.CabinetName); + Assert.AreEqual(0, ((CabFileInfo) cfi).CabinetFolderNumber); + Assert.AreEqual(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.FullName); + cfi = new CabFileInfo(cabInfo, "test01.txt"); + Assert.IsTrue(cfi.Exists); + cfi = new CabFileInfo(cabInfo, "test01.txt"); + Assert.AreEqual(txtSize, cfi.Length); + cfi = new CabFileInfo(cabInfo, "test00.txt"); + Assert.AreEqual(FileAttributes.Archive, cfi.Attributes); + cfi = new CabFileInfo(cabInfo, "test01.txt"); + Assert.AreEqual(FileAttributes.ReadOnly | FileAttributes.Archive, cfi.Attributes); + cfi = new CabFileInfo(cabInfo, "test01.txt"); + Assert.IsTrue((testTime - cfi.LastWriteTime).Duration() < new TimeSpan(0, 0, 5)); + Assert.AreEqual(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.ToString()); + cfi.CopyTo("testcopy.txt"); + Assert.IsTrue(File.Exists("testCopy.txt")); + Assert.AreEqual(cfi.Length, new FileInfo("testCopy.txt").Length); + + Exception caughtEx = null; + try + { + cfi.CopyTo("testcopy.txt", false); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(IOException)); + } + + [TestMethod] + public void CabFileInfoOpenText() + { + CabInfo cabInfo = new CabInfo("test.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); + + string expectedText = File.ReadAllText("test01.txt"); + + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); + + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); + using (StreamReader cabFileReader = cfi.OpenText()) + { + string text = cabFileReader.ReadToEnd(); + Assert.AreEqual(expectedText, text); + + // Check the assumption that the cab can't be deleted while a stream is open. + Exception caughtEx = null; + try + { + File.Delete(cabInfo.FullName); + } + catch (Exception ex) + { + caughtEx = ex; + } + + Assert.IsInstanceOfType(caughtEx, typeof(IOException)); + } + + // Ensure all streams are closed after disposing of the StreamReader returned by OpenText. + File.Delete(cabInfo.FullName); + } + + [TestMethod] + public void CabFileInfoNullParams() + { + Exception caughtEx; + CabInfo cabInfo = new CabInfo("test.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt"); + + caughtEx = null; + try + { + new CabFileInfo(null, "test00.txt"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + caughtEx = null; + try + { + new CabFileInfo(cabInfo, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + caughtEx = null; + try + { + cfi.CopyTo(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + } + + [TestMethod] + public void CabInfoSerialization() + { + CabInfo cabInfo = new CabInfo("testser.cab"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("testser00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("testser01.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "testser00.txt", "testser01.txt" }, null); + ArchiveFileInfo cfi = cabInfo.GetFiles()[1]; + + MemoryStream memStream = new MemoryStream(); + + BinaryFormatter formatter = new BinaryFormatter(); + + memStream.Seek(0, SeekOrigin.Begin); + formatter.Serialize(memStream, cabInfo); + memStream.Seek(0, SeekOrigin.Begin); + CabInfo cabInfo2 = (CabInfo) formatter.Deserialize(memStream); + Assert.AreEqual(cabInfo.FullName, cabInfo2.FullName); + + memStream.Seek(0, SeekOrigin.Begin); + formatter.Serialize(memStream, cfi); + memStream.Seek(0, SeekOrigin.Begin); + CabFileInfo cfi2 = (CabFileInfo) formatter.Deserialize(memStream); + Assert.AreEqual(cfi.FullName, cfi2.FullName); + Assert.AreEqual(cfi.Length, cfi2.Length); + + CabException cabEx = new CabException(); + memStream.Seek(0, SeekOrigin.Begin); + formatter.Serialize(memStream, cabEx); + memStream.Seek(0, SeekOrigin.Begin); + formatter.Deserialize(memStream); + + cabEx = new CabException("Test exception.", null); + Assert.AreEqual("Test exception.", cabEx.Message); + } + + [TestMethod] + public void CabFileStreamContextNullParams() + { + ArchiveFileStreamContext streamContext = null; + Exception caughtEx = null; + try + { + streamContext = new ArchiveFileStreamContext(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing null to constructor."); + caughtEx = null; + try + { + streamContext = new ArchiveFileStreamContext(new string[] { }, "testDir", new Dictionary()); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing 0-length array to constructor."); + caughtEx = null; + try + { + streamContext = new ArchiveFileStreamContext(new string[] { "test.cab" }, null, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx); + } + + [TestMethod] + public void CabinetTruncateOnCreate() + { + CabInfo cabInfo = new CabInfo("testtruncate.cab"); + int txtSize = 20240; + CompressionTestUtil.GenerateRandomFile("testtruncate0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("testtruncate1.txt", 1, txtSize); + cabInfo.PackFiles(null, new string[] { "testtruncate0.txt", "testtruncate1.txt" }, null); + + long size1 = cabInfo.Length; + + txtSize /= 5; + CompressionTestUtil.GenerateRandomFile("testtruncate2.txt", 2, txtSize); + CompressionTestUtil.GenerateRandomFile("testtruncate3.txt", 3, txtSize); + cabInfo.PackFiles(null, new string[] { "testtruncate2.txt", "testtruncate3.txt" }, null); + + // The newly created cab file should be smaller than before. + Assert.AreNotEqual(size1, cabInfo.Length, "Checking that cabinet file got truncated when creating a smaller cab in-place."); + } + + [TestMethod] + public void CabTruncatedArchive() + { + CabInfo cabInfo = new CabInfo("test-t.cab"); + CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, 5); + CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, 5); + cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null); + + CompressionTestUtil.TestTruncatedArchive(cabInfo, typeof(CabException)); + } + private const string TEST_FILENAME_PREFIX = "\x20AC"; + + private IList RunCabinetPackUnpack(int fileCount, long fileSize) + { + return RunCabinetPackUnpack(fileCount, fileSize, 0, 0); + } + private IList RunCabinetPackUnpack(int fileCount, long fileSize, + long maxFolderSize, long maxArchiveSize) + { + return this.RunCabinetPackUnpack(fileCount, fileSize, maxFolderSize, maxArchiveSize, CompressionLevel.Normal); + } + private IList RunCabinetPackUnpack(int fileCount, long fileSize, + long maxFolderSize, long maxArchiveSize, CompressionLevel compLevel) + { + Console.WriteLine("Creating cabinet with {0} files of size {1}", + fileCount, fileSize); + Console.WriteLine("MaxFolderSize={0}, MaxArchiveSize={1}, CompressionLevel={2}", + maxFolderSize, maxArchiveSize, compLevel); + + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + + string[] archiveNames = new string[100]; + for (int i = 0; i < archiveNames.Length; i++) + { + archiveNames[i] = String.Format("{0}-{1}{2}{3}.cab", fileCount, fileSize, + (i == 0 ? "" : "-"), (i == 0 ? "" : i.ToString())); + } + + string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize); + CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile); + + IList fileInfo; + using (CabEngine cabEngine = new CabEngine()) + { + cabEngine.CompressionLevel = compLevel; + + File.AppendAllText(progressTextFile, + "\r\n\r\n====================================================\r\nCREATE\r\n\r\n"); + cabEngine.Progress += testUtil.PrintArchiveProgress; + + OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null); + if (maxFolderSize == 1) + { + streamContext.OptionHandler = + delegate(string optionName, object[] parameters) + { + if (optionName == "nextFolder") return true; + return null; + }; + } + else if (maxFolderSize > 1) + { + streamContext.OptionHandler = + delegate(string optionName, object[] parameters) + { + if (optionName == "maxFolderSize") return maxFolderSize; + return null; + }; + } + cabEngine.Pack(streamContext, files, maxArchiveSize); + + IList createdArchiveNames = new List(archiveNames.Length); + for (int i = 0; i < archiveNames.Length; i++) + { + if (File.Exists(archiveNames[i])) + { + createdArchiveNames.Add(archiveNames[i]); + } + else + { + break; + } + } + + Console.WriteLine("Listing cabinet with {0} files of size {1}", + fileCount, fileSize); + File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n"); + fileInfo = cabEngine.GetFileInfo( + new ArchiveFileStreamContext(createdArchiveNames, null, null), null); + + Assert.AreEqual(fileCount, fileInfo.Count); + if (fileCount > 0) + { + int folders = ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber + 1; + if (maxFolderSize == 1) + { + Assert.AreEqual(fileCount, folders); + } + } + + Console.WriteLine("Extracting cabinet with {0} files of size {1}", + fileCount, fileSize); + File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n"); + cabEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null); + } + + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether cabinet output directory matches input directory."); + + return fileInfo; + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj new file mode 100644 index 00000000..636cedc6 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj @@ -0,0 +1,43 @@ + + + + + {4544158C-2D63-4146-85FF-62169280144E} + Library + WixToolsetTests.Dtf.Cab + WixToolsetTests.Dtf.Cab + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + v4.7.2 + + + + + + + + + + + + + + + + {45D81DAB-0559-4836-8106-CE9987FD4AB5} + WixToolset.Dtf.Compression + + + {E56C0ED3-FA2F-4CA9-A1C0-2E796BB0BF80} + WixToolset.Dtf.Compression.Cab + + + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} + WixToolsetTests.Dtf.Compression + + + + + + diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj new file mode 100644 index 00000000..d46776d8 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj @@ -0,0 +1,41 @@ + + + + + {328799BB-7B03-4B28-8180-4132211FD07D} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.Zip + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + v4.7.2 + + + + + + + + + + + + + + {45D81DAB-0559-4836-8106-CE9987FD4AB5} + WixToolset.Dtf.Compression + + + {E4C60A57-8AFE-4FF3-9058-ACAC6A069533} + WixToolset.Dtf.Compression.Zip + + + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} + WixToolsetTests.Dtf.Compression + + + + + + diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs new file mode 100644 index 00000000..b264ad5b --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs @@ -0,0 +1,518 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Text; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.Compression; + using WixToolset.Dtf.Compression.Zip; + + [TestClass] + public class ZipTest + { + public ZipTest() + { + } + + [TestInitialize] + public void Initialize() + { + } + + [TestCleanup] + public void Cleanup() + { + } + + [TestMethod] + public void ZipFileCounts() + { + this.RunZipPackUnpack(0, 10, 0); + this.RunZipPackUnpack(0, 100000, 0); + this.RunZipPackUnpack(1, 10, 0); + this.RunZipPackUnpack(100, 10, 0); + } + + [TestMethod] + [Ignore] // Takes too long to run regularly. + public void ZipExtremeFileCounts() + { + this.RunZipPackUnpack(66000, 10, 0); + } + + [TestMethod] + public void ZipFileSizes() + { + this.RunZipPackUnpack(1, 0, 0); + for (int n = 1; n <= 33; n++) + { + this.RunZipPackUnpack(1, n, 0); + } + this.RunZipPackUnpack(1, 100 * 1024, 0); + this.RunZipPackUnpack(1, 10 * 1024 * 1024, 0); + } + + [Timeout(36000000), TestMethod] + [Ignore] // Takes too long to run regularly. + public void ZipExtremeFileSizes() + { + //this.RunZipPackUnpack(10, 512L * 1024 * 1024, 0); // 5GB + this.RunZipPackUnpack(1, 5L * 1024 * 1024 * 1024, 0, CompressionLevel.None); // 5GB + } + + [TestMethod] + public void ZipArchiveCounts() + { + IList fileInfo; + fileInfo = this.RunZipPackUnpack(10, 100 * 1024, 400 * 1024, CompressionLevel.None); + Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, + "Testing whether archive spans the correct # of zip files."); + + fileInfo = this.RunZipPackUnpack(2, 90 * 1024, 40 * 1024, CompressionLevel.None); + Assert.AreEqual(2, fileInfo[fileInfo.Count - 1].ArchiveNumber, + "Testing whether archive spans the correct # of zip files."); + } + + [TestMethod] + public void ZipProgress() + { + CompressionTestUtil.ExpectedProgress = new List(new int[][] { + // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 1 }, + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 2 }, + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 }, + // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 3 }, + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 3 }, + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 }, + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 }, + }); + CompressionTestUtil.ExpectedProgress = null; + + try + { + this.RunZipPackUnpack(15, 20 * 1024, 130 * 1024, CompressionLevel.None); + } + finally + { + CompressionTestUtil.ExpectedProgress = null; + } + } + + [TestMethod] + //[Ignore] // Requires clean environment. + public void ZipArchiveSizes() + { + Console.WriteLine("Testing various values for the maxArchiveSize parameter."); + this.RunZipPackUnpack(5, 1024, Int64.MinValue); + this.RunZipPackUnpack(5, 1024, -1); + this.RunZipPackUnpack(2, 10, 0); + + this.RunZipPackUnpack(1, 10, 1); + this.RunZipPackUnpack(2, 10, 2); + this.RunZipPackUnpack(2, 10, 3); + this.RunZipPackUnpack(2, 10, 4); + this.RunZipPackUnpack(2, 10, 5); + this.RunZipPackUnpack(2, 10, 6); + this.RunZipPackUnpack(2, 10, 7); + this.RunZipPackUnpack(5, 10, 8); + this.RunZipPackUnpack(5, 10, 9); + this.RunZipPackUnpack(5, 10, 10); + this.RunZipPackUnpack(5, 10, 11); + this.RunZipPackUnpack(5, 10, 12); + + this.RunZipPackUnpack(5, 101, 255); + this.RunZipPackUnpack(5, 102, 256); + this.RunZipPackUnpack(5, 103, 257); + this.RunZipPackUnpack(5, 24000, 32768); + this.RunZipPackUnpack(5, 1024, Int64.MaxValue); + } + + [TestMethod] + public void ZipCompLevelParam() + { + Console.WriteLine("Testing various values for the compressionLevel parameter."); + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.None); + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Min); + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Normal); + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Max); + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.None - 1)); + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1)); + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MinValue); + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MaxValue); + } + + [TestMethod] + public void ZipInfoGetFiles() + { + IList fileInfos; + ZipInfo zipInfo = new ZipInfo("testgetfiles.zip"); + + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize); + CompressionTestUtil.GenerateRandomFile("testinfo2.ini", 2, txtSize); + zipInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt", "testinfo2.ini" }, null); + + fileInfos = zipInfo.GetFiles(); + Assert.IsNotNull(fileInfos); + Assert.AreEqual(3, fileInfos.Count); + Assert.AreEqual("testinfo0.txt", fileInfos[0].Name); + Assert.AreEqual("testinfo1.txt", fileInfos[1].Name); + Assert.AreEqual("testinfo2.ini", fileInfos[2].Name); + + fileInfos = zipInfo.GetFiles("*.txt"); + Assert.IsNotNull(fileInfos); + Assert.AreEqual(2, fileInfos.Count); + Assert.AreEqual("testinfo0.txt", fileInfos[0].Name); + Assert.AreEqual("testinfo1.txt", fileInfos[1].Name); + + fileInfos = zipInfo.GetFiles("testinfo1.txt"); + Assert.IsNotNull(fileInfos); + Assert.AreEqual(1, fileInfos.Count); + Assert.AreEqual("testinfo1.txt", fileInfos[0].Name); + Assert.IsTrue(DateTime.Now - fileInfos[0].LastWriteTime < TimeSpan.FromMinutes(1), + "Checking ZipFileInfo.LastWriteTime is current."); + } + + [TestMethod] + //[Ignore] // Requires clean environment. + public void ZipInfoNullParams() + { + int fileCount = 10, fileSize = 1024; + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = "zipinfo-" + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + + ZipInfo zipInfo = new ZipInfo("testnull.zip"); + + CompressionTestUtil.TestArchiveInfoNullParams(zipInfo, dirA, dirB, files); + } + + [TestMethod] + public void ZipFileInfoNullParams() + { + Exception caughtEx; + ZipInfo zipInfo = new ZipInfo("test.zip"); + int txtSize = 10240; + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize); + zipInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null); + ZipFileInfo zfi = new ZipFileInfo(zipInfo, "test01.txt"); + + caughtEx = null; + try + { + new ZipFileInfo(null, "test00.txt"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + caughtEx = null; + try + { + new ZipFileInfo(zipInfo, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + caughtEx = null; + try + { + zfi.CopyTo(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException)); + } + + [TestMethod] + public void ZipEngineNullParams() + { + string[] testFiles = new string[] { "test.txt" }; + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.zip", null, null); + + using (ZipEngine zipEngine = new ZipEngine()) + { + zipEngine.CompressionLevel = CompressionLevel.None; + + CompressionTestUtil.TestCompressionEngineNullParams(zipEngine, streamContext, testFiles); + } + } + + [TestMethod] + public void ZipBadPackStreamContexts() + { + string[] testFiles = new string[] { "test.txt" }; + CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000); + + using (ZipEngine zipEngine = new ZipEngine()) + { + zipEngine.CompressionLevel = CompressionLevel.None; + + CompressionTestUtil.TestBadPackStreamContexts(zipEngine, "test.zip", testFiles); + } + } + + [TestMethod] + public void ZipBadUnpackStreamContexts() + { + int txtSize = 40960; + ZipInfo zipInfo = new ZipInfo("test2.zip"); + CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, txtSize); + CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, txtSize); + zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null); + + using (ZipEngine zipEngine = new ZipEngine()) + { + CompressionTestUtil.TestBadUnpackStreamContexts(zipEngine, "test2.zip"); + } + } + + [TestMethod] + [Ignore] // Failed on build server, need to investigate. + public void ZipTruncatedArchive() + { + ZipInfo zipInfo = new ZipInfo("test-t.zip"); + CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, 5); + CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, 5); + zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null); + + CompressionTestUtil.TestTruncatedArchive(zipInfo, typeof(ZipException)); + } + + /* + [TestMethod] + public void ZipUnpack() + { + IList fileInfos; + foreach (FileInfo zipFile in new DirectoryInfo("D:\\temp").GetFiles("*.zip")) + { + Console.WriteLine("====================================================="); + Console.WriteLine(zipFile.FullName); + Console.WriteLine("====================================================="); + ZipInfo zipTest = new ZipInfo(zipFile.FullName); + fileInfos = zipTest.GetFiles(); + Assert.AreNotEqual(0, fileInfos.Count); + foreach (ArchiveFileInfo file in fileInfos) + { + Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime); + } + + Directory.CreateDirectory(Path.GetFileNameWithoutExtension(zipFile.Name)); + zipTest.Unpack(Path.GetFileNameWithoutExtension(zipFile.Name)); + } + } + */ + + /* + [TestMethod] + public void ZipUnpackSelfExtractor() + { + ZipInfo zipTest = new ZipInfo(@"C:\temp\testzip.exe"); + IList fileInfos = zipTest.GetFiles(); + Assert.AreNotEqual(0, fileInfos.Count); + foreach (ArchiveFileInfo file in fileInfos) + { + Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime); + } + + string extractDir = Path.GetFileNameWithoutExtension(zipTest.Name); + Directory.CreateDirectory(extractDir); + zipTest.Unpack(extractDir); + } + */ + + private const string TEST_FILENAME_PREFIX = "\x20AC"; + + private IList RunZipPackUnpack(int fileCount, long fileSize, + long maxArchiveSize) + { + return this.RunZipPackUnpack(fileCount, fileSize, maxArchiveSize, CompressionLevel.Normal); + } + + private IList RunZipPackUnpack(int fileCount, long fileSize, + long maxArchiveSize, CompressionLevel compLevel) + { + Console.WriteLine("Creating zip archive with {0} files of size {1}", + fileCount, fileSize); + Console.WriteLine("MaxArchiveSize={0}, CompressionLevel={1}", maxArchiveSize, compLevel); + + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize); + if (Directory.Exists(dirA)) Directory.Delete(dirA, true); + Directory.CreateDirectory(dirA); + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize); + if (Directory.Exists(dirB)) Directory.Delete(dirB, true); + Directory.CreateDirectory(dirB); + + string[] files = new string[fileCount]; + for (int iFile = 0; iFile < fileCount; iFile++) + { + files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt"; + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize); + } + + string[] archiveNames = new string[1000]; + for (int i = 0; i < archiveNames.Length; i++) + { + if (i < 100) + { + archiveNames[i] = String.Format( + (i == 0 ? "{0}-{1}.zip" : "{0}-{1}.z{2:d02}"), + fileCount, fileSize, i); + } + else + { + archiveNames[i] = String.Format( + "{0}-{1}.{2:d03}", fileCount, fileSize, i); + } + } + + string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize); + CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile); + + IList fileInfo; + using (ZipEngine zipEngine = new ZipEngine()) + { + zipEngine.CompressionLevel = compLevel; + + File.AppendAllText(progressTextFile, + "\r\n\r\n====================================================\r\nCREATE\r\n\r\n"); + zipEngine.Progress += testUtil.PrintArchiveProgress; + + OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null); + streamContext.OptionHandler = + delegate(string optionName, object[] parameters) + { + // For testing purposes, force zip64 for only moderately large files. + switch (optionName) + { + case "forceZip64": + return fileSize > UInt16.MaxValue; + default: + return null; + } + }; + + zipEngine.Pack(streamContext, files, maxArchiveSize); + + string checkArchiveName = archiveNames[0]; + if (File.Exists(archiveNames[1])) checkArchiveName = archiveNames[1]; + using (Stream archiveStream = File.OpenRead(checkArchiveName)) + { + bool isArchive = zipEngine.IsArchive(archiveStream); + Assert.IsTrue(isArchive, "Checking that created archive appears valid."); + } + + IList createdArchiveNames = new List(archiveNames.Length); + for (int i = 0; i < archiveNames.Length; i++) + { + if (File.Exists(archiveNames[i])) + { + createdArchiveNames.Add(archiveNames[i]); + } + else + { + break; + } + } + + Assert.AreNotEqual(0, createdArchiveNames.Count); + + Console.WriteLine("Listing zip archive with {0} files of size {1}", + fileCount, fileSize); + File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n"); + fileInfo = zipEngine.GetFileInfo( + new ArchiveFileStreamContext(createdArchiveNames, null, null), null); + + Assert.AreEqual(fileCount, fileInfo.Count); + + Console.WriteLine("Extracting zip archive with {0} files of size {1}", + fileCount, fileSize); + File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n"); + zipEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null); + } + + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB); + Assert.IsTrue(directoryMatch, + "Testing whether zip output directory matches input directory."); + + return fileInfo; + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs new file mode 100644 index 00000000..e7a5373d --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs @@ -0,0 +1,649 @@ +// 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. + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using WixToolset.Dtf.Compression; + +namespace WixToolset.Dtf.Test +{ + public class CompressionTestUtil + { + private static MD5 md5 = new MD5CryptoServiceProvider(); + + private string progressTextFile; + + public CompressionTestUtil(string progressTextFile) + { + this.progressTextFile = progressTextFile; + } + + public static IList ExpectedProgress + { + get { return CompressionTestUtil.expectedProgress; } + set { CompressionTestUtil.expectedProgress = value; } + } + private static IList expectedProgress; + + public void PrintArchiveProgress(object source, ArchiveProgressEventArgs e) + { + switch (e.ProgressType) + { + case ArchiveProgressType.StartFile: + { + Console.WriteLine("StartFile: {0}", e.CurrentFileName); + } break; + case ArchiveProgressType.FinishFile: + { + Console.WriteLine("FinishFile: {0}", e.CurrentFileName); + } break; + case ArchiveProgressType.StartArchive: + { + Console.WriteLine("StartArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName); + } break; + case ArchiveProgressType.FinishArchive: + { + Console.WriteLine("FinishArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName); + } break; + } + + File.AppendAllText(this.progressTextFile, e.ToString().Replace("\n", Environment.NewLine)); + + if (CompressionTestUtil.expectedProgress != null && + e.ProgressType != ArchiveProgressType.PartialFile && + e.ProgressType != ArchiveProgressType.PartialArchive) + { + Assert.AreNotEqual(0, CompressionTestUtil.expectedProgress.Count); + int[] expected = CompressionTestUtil.expectedProgress[0]; + CompressionTestUtil.expectedProgress.RemoveAt(0); + Assert.AreEqual((ArchiveProgressType) expected[0], e.ProgressType, "Checking ProgressType."); + Assert.AreEqual(expected[1], e.CurrentFileNumber, "Checking CurrentFileNumber."); + Assert.AreEqual(expected[2], e.TotalFiles, "Checking TotalFiles."); + Assert.AreEqual(expected[4], e.CurrentArchiveNumber, "Checking CurrentArchiveNumber."); + Assert.AreEqual(expected[5], e.TotalArchives, "Checking TotalArchives."); + } + } + + public static bool CompareDirectories(string dirA, string dirB) + { + bool difference = false; + Console.WriteLine("Comparing directories {0}, {1}", dirA, dirB); + + string[] filesA = Directory.GetFiles(dirA); + string[] filesB = Directory.GetFiles(dirB); + for (int iA = 0; iA < filesA.Length; iA++) + { + filesA[iA] = Path.GetFileName(filesA[iA]); + } + for (int iB = 0; iB < filesB.Length; iB++) + { + filesB[iB] = Path.GetFileName(filesB[iB]); + } + Array.Sort(filesA); + Array.Sort(filesB); + + for (int iA = 0, iB = 0; iA < filesA.Length || iB < filesB.Length; ) + { + int comp; + if (iA == filesA.Length) + { + comp = 1; + } + else if (iB == filesB.Length) + { + comp = -1; + } + else + { + comp = String.Compare(filesA[iA], filesB[iB]); + } + if (comp < 0) + { + Console.WriteLine("< " + filesA[iA]); + difference = true; + iA++; + } + else if (comp > 0) + { + Console.WriteLine("> " + filesB[iB]); + difference = true; + iB++; + } + else + { + string fileA = Path.Combine(dirA, filesA[iA]); + string fileB = Path.Combine(dirB, filesB[iB]); + + byte[] hashA; + byte[] hashB; + + lock (CompressionTestUtil.md5) + { + using (Stream fileAStream = File.OpenRead(fileA)) + { + hashA = CompressionTestUtil.md5.ComputeHash(fileAStream); + } + using (Stream fileBStream = File.OpenRead(fileB)) + { + hashB = CompressionTestUtil.md5.ComputeHash(fileBStream); + } + } + + for (int i = 0; i < hashA.Length; i++) + { + if (hashA[i] != hashB[i]) + { + Console.WriteLine("~ " + filesA[iA]); + difference = true; + break; + } + } + + iA++; + iB++; + } + } + + string[] dirsA = Directory.GetDirectories(dirA); + string[] dirsB = Directory.GetDirectories(dirB); + for (int iA = 0; iA < dirsA.Length; iA++) + { + dirsA[iA] = Path.GetFileName(dirsA[iA]); + } + for (int iB = 0; iB < dirsB.Length; iB++) + { + dirsB[iB] = Path.GetFileName(dirsB[iB]); + } + Array.Sort(dirsA); + Array.Sort(dirsB); + + for (int iA = 0, iB = 0; iA < dirsA.Length || iB < dirsB.Length; ) + { + int comp; + if (iA == dirsA.Length) + { + comp = 1; + } + else if (iB == dirsB.Length) + { + comp = -1; + } + else + { + comp = String.Compare(dirsA[iA], dirsB[iB]); + } + if (comp < 0) + { + Console.WriteLine("< {0}\\", dirsA[iA]); + difference = true; + iA++; + } + else if (comp > 0) + { + Console.WriteLine("> {1}\\", dirsB[iB]); + difference = true; + iB++; + } + else + { + string subDirA = Path.Combine(dirA, dirsA[iA]); + string subDirB = Path.Combine(dirB, dirsB[iB]); + if (!CompressionTestUtil.CompareDirectories(subDirA, subDirB)) + { + difference = true; + } + iA++; + iB++; + } + } + + return !difference; + } + + + public static void GenerateRandomFile(string path, int seed, long size) + { + Console.WriteLine("Generating random file {0} (seed={1}, size={2})", + path, seed, size); + Random random = new Random(seed); + bool easy = random.Next(2) == 1; + int chunk = 1024 * random.Next(1, 100); + using (TextWriter tw = new StreamWriter( + File.Create(path, 4096), Encoding.ASCII)) + { + for (long count = 0; count < size; count++) + { + char c = (char) (easy ? random.Next('a', 'b' + 1) + : random.Next(32, 127)); + tw.Write(c); + if (--chunk == 0) + { + chunk = 1024 * random.Next(1, 101); + easy = random.Next(2) == 1; + } + } + } + } + + public static void TestArchiveInfoNullParams( + ArchiveInfo archiveInfo, + string dirA, + string dirB, + string[] files) + { + Exception caughtEx = null; + try + { + archiveInfo.PackFiles(null, null, files); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.PackFiles(null, files, new string[] { }); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.PackFileSet(dirA, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.PackFiles(null, files, files); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.PackFiles(dirA, null, files); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.PackFiles(dirA, files, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); + + caughtEx = null; + try + { + archiveInfo.CopyTo(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.CopyTo(null, true); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.MoveTo(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.GetFiles(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFile(null, "test.txt"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFile("test.txt", null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFiles(null, dirB, files); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFiles(files, null, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFiles(files, null, files); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFiles(files, dirB, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFiles(files, dirB, new string[] { }); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + archiveInfo.UnpackFileSet(null, dirB); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + } + + public static void TestCompressionEngineNullParams( + CompressionEngine engine, + ArchiveFileStreamContext streamContext, + string[] testFiles) + { + Exception caughtEx; + + Console.WriteLine("Testing null streamContext."); + caughtEx = null; + try + { + engine.Pack(null, testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.Pack(null, testFiles, 0); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + + Console.WriteLine("Testing null files."); + caughtEx = null; + try + { + engine.Pack(streamContext, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + + Console.WriteLine("Testing null files."); + caughtEx = null; + try + { + engine.Pack(streamContext, null, 0); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + + + Console.WriteLine("Testing null stream."); + caughtEx = null; + try + { + engine.IsArchive(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.FindArchiveOffset(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.GetFiles(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.GetFileInfo(null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.Unpack(null, "testUnpack.txt"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + Console.WriteLine("Testing null streamContext."); + caughtEx = null; + try + { + engine.GetFiles(null, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.GetFileInfo(null, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + caughtEx = null; + try + { + engine.Unpack((IUnpackStreamContext) null, null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx); + } + + public static void TestBadPackStreamContexts( + CompressionEngine engine, string archiveName, string[] testFiles) + { + Exception caughtEx; + + Console.WriteLine("Testing streamContext that returns null from GetName."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, false, false, true, true, true, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that returns null from OpenArchive."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that returns null from OpenFile."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on GetName."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, true, false, true, true, true, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on OpenArchive."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on CloseArchive."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on OpenFile."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on CloseFile."); + caughtEx = null; + try + { + engine.Pack( + new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), + testFiles); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + } + + public static void TestBadUnpackStreamContexts( + CompressionEngine engine, string archiveName) + { + Exception caughtEx; + + Console.WriteLine("Testing streamContext that returns null from OpenArchive."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that returns null from OpenFile."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on OpenArchive."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on CloseArchive."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on OpenFile."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + Console.WriteLine("Testing streamContext that throws on CloseFile."); + caughtEx = null; + try + { + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), null); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx); + } + + public static void TestTruncatedArchive( + ArchiveInfo archiveInfo, Type expectedExceptionType) + { + for (long len = archiveInfo.Length - 1; len >= 0; len--) + { + string testArchive = String.Format("{0}.{1:d06}", + archiveInfo.FullName, len); + if (File.Exists(testArchive)) + { + File.Delete(testArchive); + } + + archiveInfo.CopyTo(testArchive); + using (FileStream truncateStream = + File.Open(testArchive, FileMode.Open, FileAccess.ReadWrite)) + { + truncateStream.SetLength(len); + } + + ArchiveInfo testArchiveInfo = (ArchiveInfo) archiveInfo.GetType() + .GetConstructor(new Type[] { typeof(string) }).Invoke(new object[] { testArchive }); + + Exception caughtEx = null; + try + { + testArchiveInfo.GetFiles(); + } + catch (Exception ex) { caughtEx = ex; } + File.Delete(testArchive); + + if (caughtEx != null) + { + Assert.IsInstanceOfType(caughtEx, expectedExceptionType, + String.Format("Caught exception listing archive truncated to {0}/{1} bytes", + len, archiveInfo.Length)); + } + } + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs new file mode 100644 index 00000000..2531f3bc --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs @@ -0,0 +1,202 @@ +// 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. + +using System; +using System.IO; +using System.Collections.Generic; +using WixToolset.Dtf.Compression; + +namespace WixToolset.Dtf.Test +{ + public class MisbehavingStreamContext : ArchiveFileStreamContext + { + public const string EXCEPTION = "Test exception."; + + private bool throwEx; + private bool getName; + private bool openArchive; + private bool closeArchive; + private bool openFile; + private bool closeFile; + private int closeFileCount; + + public MisbehavingStreamContext( + string cabinetFile, + string directory, + IDictionary files, + bool throwEx, + bool getName, + bool openArchive, + bool closeArchive, + bool openFile, + bool closeFile) + : base(cabinetFile, directory, files) + { + this.throwEx = throwEx; + this.getName = getName; + this.openArchive = openArchive; + this.closeArchive = closeArchive; + this.openFile = openFile; + this.closeFile = closeFile; + } + + public override string GetArchiveName(int archiveNumber) + { + if (!this.getName) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + else + { + return null; + } + } + return base.GetArchiveName(archiveNumber); + } + + public override Stream OpenArchiveWriteStream( + int archiveNumber, + string archiveName, + bool truncate, + CompressionEngine compressionEngine) + { + if (!this.openArchive) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + else + { + return null; + } + } + return base.OpenArchiveWriteStream( + archiveNumber, archiveName, truncate, compressionEngine); + } + + public override void CloseArchiveWriteStream( + int archiveNumber, + string archiveName, + Stream stream) + { + if (!this.closeArchive) + { + if (throwEx) + { + this.closeArchive = true; + throw new Exception(EXCEPTION); + } + return; + } + base.CloseArchiveWriteStream(archiveNumber, archiveName, stream); + } + + public override Stream OpenFileReadStream( + string path, + out FileAttributes attributes, + out DateTime lastWriteTime) + { + if (!this.openFile) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + else + { + attributes = FileAttributes.Normal; + lastWriteTime = DateTime.MinValue; + return null; + } + } + return base.OpenFileReadStream(path, out attributes, out lastWriteTime); + } + + public override void CloseFileReadStream(string path, Stream stream) + { + if (!this.closeFile && ++closeFileCount == 2) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + return; + } + base.CloseFileReadStream(path, stream); + } + + public override Stream OpenArchiveReadStream( + int archiveNumber, + string archiveName, + CompressionEngine compressionEngine) + { + if (!this.openArchive) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + else + { + return null; + } + } + return base.OpenArchiveReadStream(archiveNumber, archiveName, compressionEngine); + } + + public override void CloseArchiveReadStream( + int archiveNumber, + string archiveName, + Stream stream) + { + if (!this.closeArchive) + { + if (throwEx) + { + this.closeArchive = true; + throw new Exception(EXCEPTION); + } + return; + } + base.CloseArchiveReadStream(archiveNumber, archiveName, stream); + } + + public override Stream OpenFileWriteStream( + string path, + long fileSize, + DateTime lastWriteTime) + { + if (!this.openFile) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + else + { + return null; + } + } + return base.OpenFileWriteStream(path, fileSize, lastWriteTime); + } + + public override void CloseFileWriteStream( + string path, + Stream stream, + FileAttributes attributes, + DateTime lastWriteTime) + { + if (!this.closeFile && ++closeFileCount == 2) + { + if (throwEx) + { + throw new Exception(EXCEPTION); + } + return; + } + base.CloseFileWriteStream(path, stream, attributes, lastWriteTime); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs new file mode 100644 index 00000000..98354d97 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +using System; +using System.Collections.Generic; +using WixToolset.Dtf.Compression; + +namespace WixToolset.Dtf.Test +{ + public class OptionStreamContext : ArchiveFileStreamContext + { + private PackOptionHandler packOptionHandler; + + public OptionStreamContext(IList archiveFiles, string directory, IDictionary files) + : base(archiveFiles, directory, files) + { + } + + public delegate object PackOptionHandler(string optionName, object[] parameters); + + public PackOptionHandler OptionHandler + { + get + { + return this.packOptionHandler; + } + set + { + this.packOptionHandler = value; + } + } + + public override object GetOption(string optionName, object[] parameters) + { + if (this.OptionHandler == null) + { + return null; + } + + return this.OptionHandler(optionName, parameters); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj new file mode 100644 index 00000000..628d36c5 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj @@ -0,0 +1,36 @@ + + + + + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.Compression + false + false + v4.7.2 + + + + + + + + + + + + + + + + + + {45D81DAB-0559-4836-8106-CE9987FD4AB5} + WixToolset.Dtf.Compression + + + + + + diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs new file mode 100644 index 00000000..bf843024 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs @@ -0,0 +1,206 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Text; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + + [TestClass] + public class CustomActionTest + { + public CustomActionTest() + { + } + + [TestMethod] + [Ignore] // Currently fails. + public void CustomActionTest1() + { + InstallLogModes logEverything = + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog; + + Installer.SetInternalUI(InstallUIOptions.Silent); + ExternalUIHandler prevHandler = Installer.SetExternalUI( + WindowsInstallerTest.ExternalUILogger, logEverything); + + try + { + string[] customActions = new string[] { "SampleCA1", "SampleCA2" }; + #if DEBUG + string caDir = @"..\..\..\..\..\build\debug\x86\"; + #else + string caDir = @"..\..\..\..\..\build\ship\x86\"; + #endif + caDir = Path.GetFullPath(caDir); + string caFile = "WixToolset.Dtf.Samples.ManagedCA.dll"; + string caProduct = "CustomActionTest.msi"; + + this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, false); + + Exception caughtEx = null; + try + { + Installer.InstallProduct(caProduct, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException), + "Exception thrown while installing product: " + caughtEx); + + string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + string arch2 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); + if (arch == "AMD64" || arch2 == "AMD64") + { + caDir = caDir.Replace("x86", "x64"); + + this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, true); + + caughtEx = null; + try + { + Installer.InstallProduct(caProduct, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException), + "Exception thrown while installing 64bit product: " + caughtEx); + } + } + finally + { + Installer.SetExternalUI(prevHandler, InstallLogModes.None); + } + } + + private void CreateCustomActionProduct( + string msiFile, string customActionFile, IList customActions, bool sixtyFourBit) + { + using (Database db = new Database(msiFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db, sixtyFourBit); + WindowsInstallerUtils.CreateTestProduct(db); + + if (!File.Exists(customActionFile)) + throw new FileNotFoundException(customActionFile); + + using (Record binRec = new Record(2)) + { + binRec[1] = Path.GetFileName(customActionFile); + binRec.SetStream(2, customActionFile); + + db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec); + } + + using (Record binRec2 = new Record(2)) + { + binRec2[1] = "TestData"; + binRec2.SetStream(2, new MemoryStream(Encoding.UTF8.GetBytes("This is a test data stream."))); + + db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec2); + } + + for (int i = 0; i < customActions.Count; i++) + { + db.Execute( + "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('{0}', 1, '{1}', '{2}')", + customActions[i], + Path.GetFileName(customActionFile), + customActions[i]); + db.Execute( + "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('{0}', '', {1})", + customActions[i], + 101 + i); + } + + db.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES ('SampleCATest', 'TestValue')"); + + db.Commit(); + } + } + + [TestMethod] + public void CustomActionData() + { + string dataString = "Key1=Value1;Key2=;Key3;Key4=Value=4;Key5"; + string dataString2 = "Key1=;Key2=Value2;Key3;Key4;Key6=Value;;6=6;Key7=Value7"; + + CustomActionData data = new CustomActionData(dataString); + Assert.AreEqual(dataString, data.ToString()); + + data["Key1"] = String.Empty; + data["Key2"] = "Value2"; + data["Key4"] = null; + data.Remove("Key5"); + data["Key6"] = "Value;6=6"; + data["Key7"] = "Value7"; + + Assert.AreEqual(dataString2, data.ToString()); + + MyDataClass myData = new MyDataClass(); + myData.Member1 = "test1"; + myData.Member2 = "test2"; + data.AddObject("MyData", myData); + + string myDataString = data.ToString(); + CustomActionData data2 = new CustomActionData(myDataString); + + MyDataClass myData2 = data2.GetObject("MyData"); + Assert.AreEqual(myData, myData2); + + List myComplexDataObject = new List(); + myComplexDataObject.Add("CValue1"); + myComplexDataObject.Add("CValue2"); + myComplexDataObject.Add("CValue3"); + + CustomActionData myComplexData = new CustomActionData(); + myComplexData.AddObject("MyComplexData", myComplexDataObject); + myComplexData.AddObject("NestedData", data); + string myComplexDataString = myComplexData.ToString(); + + CustomActionData myComplexData2 = new CustomActionData(myComplexDataString); + List myComplexDataObject2 = myComplexData2.GetObject>("MyComplexData"); + + Assert.AreEqual(myComplexDataObject.Count, myComplexDataObject2.Count); + for (int i = 0; i < myComplexDataObject.Count; i++) + { + Assert.AreEqual(myComplexDataObject[i], myComplexDataObject2[i]); + } + + data2 = myComplexData2.GetObject("NestedData"); + Assert.AreEqual(data.ToString(), data2.ToString()); + } + + public class MyDataClass + { + public string Member1; + public string Member2; + + public override bool Equals(object obj) + { + MyDataClass other = obj as MyDataClass; + return other != null && this.Member1 == other.Member1 && this.Member2 == other.Member2; + } + + public override int GetHashCode() + { + return (this.Member1 != null ? this.Member1.GetHashCode() : 0) ^ + (this.Member2 != null ? this.Member2.GetHashCode() : 0); + } + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj new file mode 100644 index 00000000..a2f45fde --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj @@ -0,0 +1,32 @@ + + + + + {137D376B-989F-4FEA-9A67-01D8D38CA0DE} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.WindowsInstaller.CustomActions + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + v4.7.2 + + + + + + + + + + + + + {16F5202F-9276-4166-975C-C9654BAF8012} + WixToolsetTests.Dtf.WindowsInstaller + + + + + + diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs new file mode 100644 index 00000000..7776a1c3 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs @@ -0,0 +1,509 @@ +// 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. + +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using WixToolset.Dtf.WindowsInstaller; +using WixToolset.Dtf.WindowsInstaller.Linq; + +namespace WixToolset.Dtf.Test +{ + [TestClass] + public class LinqTest + { + private void InitLinqTestDatabase(QDatabase db) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + db.Execute( + "INSERT INTO `Feature` (`Feature`, `Title`, `Description`, `Level`, `Attributes`) VALUES ('{0}', '{1}', '{2}', {3}, {4})", + "TestFeature2", + "Test Feature 2", + "Test Feature 2 Description", + 1, + (int) FeatureAttributes.None); + + WindowsInstallerUtils.AddRegistryComponent( + db, "TestFeature2", "MyTestRegComp", + Guid.NewGuid().ToString("B"), + "SOFTWARE\\Microsoft\\DTF\\Test", + "MyTestRegComp", "test"); + WindowsInstallerUtils.AddRegistryComponent( + db, "TestFeature2", "MyTestRegComp2", + Guid.NewGuid().ToString("B"), + "SOFTWARE\\Microsoft\\DTF\\Test", + "MyTestRegComp2", "test2"); + WindowsInstallerUtils.AddRegistryComponent( + db, "TestFeature2", "excludeComp", + Guid.NewGuid().ToString("B"), + "SOFTWARE\\Microsoft\\DTF\\Test", + "MyTestRegComp3", "test3"); + + db.Commit(); + + db.Log = Console.Out; + } + + [TestMethod] + public void LinqSimple() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + var comps = from c in db.Components + select c; + + int count = 0; + foreach (var c in comps) + { + Console.WriteLine(c); + count++; + } + + Assert.AreEqual(4, count); + } + } + + [TestMethod] + public void LinqWhereNull() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + var features = from f in db.Features + where f.Description != null + select f; + + int count = 0; + foreach (var f in features) + { + Console.WriteLine(f); + Assert.AreEqual("TestFeature2", f.Feature); + count++; + } + + Assert.AreEqual(1, count); + + var features2 = from f in db.Features + where f.Description == null + select f; + + count = 0; + foreach (var f in features2) + { + Console.WriteLine(f); + Assert.AreEqual("TestFeature1", f.Feature); + count++; + } + + Assert.AreEqual(1, count); + } + } + + [TestMethod] + public void LinqWhereOperators() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + for (int i = 0; i < 100; i++) + { + var newFile = db.Files.NewRecord(); + newFile.File = "TestFile" + i; + newFile.Component_ = "TestComponent"; + newFile.FileName = "TestFile" + i + ".txt"; + newFile.FileSize = i % 10; + newFile.Sequence = i; + newFile.Insert(); + } + + var files1 = from f in db.Files where f.Sequence < 40 select f; + Assert.AreEqual(40, files1.AsEnumerable().Count()); + + var files2 = from f in db.Files where f.Sequence <= 40 select f; + Assert.AreEqual(41, files2.AsEnumerable().Count()); + + var files3 = from f in db.Files where f.Sequence > 40 select f; + Assert.AreEqual(59, files3.AsEnumerable().Count()); + + var files4 = from f in db.Files where f.Sequence >= 40 select f; + Assert.AreEqual(60, files4.AsEnumerable().Count()); + + var files5 = from f in db.Files where 40 < f.Sequence select f; + Assert.AreEqual(59, files5.AsEnumerable().Count()); + + var files6 = from f in db.Files where 40 <= f.Sequence select f; + Assert.AreEqual(60, files6.AsEnumerable().Count()); + + var files7 = from f in db.Files where 40 > f.Sequence select f; + Assert.AreEqual(40, files7.AsEnumerable().Count()); + + var files8 = from f in db.Files where 40 >= f.Sequence select f; + Assert.AreEqual(41, files8.AsEnumerable().Count()); + + var files9 = from f in db.Files where f.Sequence == 40 select f; + Assert.AreEqual(40, files9.AsEnumerable().First().Sequence); + + var files10 = from f in db.Files where f.Sequence != 40 select f; + Assert.AreEqual(99, files10.AsEnumerable().Count()); + + var files11 = from f in db.Files where 40 == f.Sequence select f; + Assert.AreEqual(40, files11.AsEnumerable().First().Sequence); + + var files12 = from f in db.Files where 40 != f.Sequence select f; + Assert.AreEqual(99, files12.AsEnumerable().Count()); + } + } + + [TestMethod] + public void LinqShapeSelect() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + Console.WriteLine("Running LINQ query 1."); + var features1 = from f in db.Features + select new { Name = f.Feature, + Desc = f.Description }; + + int count = 0; + foreach (var f in features1) + { + Console.WriteLine(f); + count++; + } + + Assert.AreEqual(2, count); + + Console.WriteLine(); + Console.WriteLine("Running LINQ query 2."); + var features2 = from f in db.Features + where f.Description != null + select new { Name = f.Feature, + Desc = f.Description.ToLower() }; + + count = 0; + foreach (var f in features2) + { + Console.WriteLine(f); + Assert.AreEqual("TestFeature2", f.Name); + count++; + } + + Assert.AreEqual(1, count); + } + } + + [TestMethod] + public void LinqUpdateNullableString() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + string newDescription = "New updated feature description."; + + var features = from f in db.Features + where f.Description != null + select f; + + int count = 0; + foreach (var f in features) + { + Console.WriteLine(f); + Assert.AreEqual("TestFeature2", f.Feature); + f.Description = newDescription; + count++; + } + + Assert.AreEqual(1, count); + + var features2 = from f in db.Features + where f.Description == newDescription + select f; + count = 0; + foreach (var f in features2) + { + Console.WriteLine(f); + Assert.AreEqual("TestFeature2", f.Feature); + f.Description = null; + count++; + } + + Assert.AreEqual(1, count); + + var features3 = from f in db.Features + where f.Description == null + select f.Feature; + count = 0; + foreach (var f in features3) + { + Console.WriteLine(f); + count++; + } + + Assert.AreEqual(2, count); + + db.Commit(); + } + } + + [TestMethod] + public void LinqInsertDelete() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + var newProp = db.Properties.NewRecord(); + newProp.Property = "TestNewProp1"; + newProp.Value = "TestNewValue"; + newProp.Insert(); + + string prop = (from p in db.Properties + where p.Property == "TestNewProp1" + select p.Value).AsEnumerable().First(); + Assert.AreEqual("TestNewValue", prop); + + newProp.Delete(); + + int propCount = (from p in db.Properties + where p.Property == "TestNewProp1" + select p.Value).AsEnumerable().Count(); + Assert.AreEqual(0, propCount); + + db.Commit(); + } + } + + [TestMethod] + public void LinqQueryQRecord() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + var installFilesSeq = (from a in db["InstallExecuteSequence"] + where a["Action"] == "InstallFiles" + select a["Sequence"]).AsEnumerable().First(); + Assert.AreEqual("4000", installFilesSeq); + } + } + + [TestMethod] + public void LinqOrderBy() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + + var actions = from a in db.InstallExecuteSequences + orderby a.Sequence + select a.Action; + foreach (var a in actions) + { + Console.WriteLine(a); + } + + var files = from f in db.Files + orderby f.FileSize, f["Sequence"] + where f.Attributes == FileAttributes.None + select f; + + foreach (var f in files) + { + Console.WriteLine(f); + } + } + } + + [TestMethod] + public void LinqTwoWayJoin() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + int count; + + var regs = from r in db.Registries + join c in db["Component"] on r.Component_ equals c["Component"] + where c["Component"] == "MyTestRegComp" && + r.Root == RegistryRoot.UserOrMachine + select new { Reg = r.Registry, Dir = c["Directory_"] }; + + count = 0; + foreach (var r in regs) + { + Console.WriteLine(r); + count++; + } + Assert.AreEqual(1, count); + + var regs2 = from r in db.Registries + join c in db.Components on r.Component_ equals c.Component + where c.Component == "MyTestRegComp" && + r.Root == RegistryRoot.UserOrMachine + select new { Reg = r, Dir = c.Directory_ }; + + count = 0; + foreach (var r in regs2) + { + Assert.IsNotNull(r.Reg.Registry); + Console.WriteLine(r); + count++; + } + Assert.AreEqual(1, count); + + var regs3 = from r in db.Registries + join c in db.Components on r.Component_ equals c.Component + where c.Component == "MyTestRegComp" && + r.Root == RegistryRoot.UserOrMachine + select r; + + count = 0; + foreach (var r in regs3) + { + Assert.IsNotNull(r.Registry); + Console.WriteLine(r); + count++; + } + Assert.AreEqual(1, count); + + } + } + + [TestMethod] + public void LinqFourWayJoin() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + int count; + + IList pretest = db.ExecuteStringQuery( + "SELECT `Feature`.`Feature` " + + "FROM `Feature`, `FeatureComponents`, `Component`, `Registry` " + + "WHERE `Feature`.`Feature` = `FeatureComponents`.`Feature_` " + + "AND `FeatureComponents`.`Component_` = `Component`.`Component` " + + "AND `Component`.`Component` = `Registry`.`Component_` " + + "AND (`Registry`.`Registry` = 'MyTestRegCompReg1')"); + Assert.AreEqual(1, pretest.Count); + + var features = from f in db.Features + join fc in db.FeatureComponents on f.Feature equals fc.Feature_ + join c in db.Components on fc.Component_ equals c.Component + join r in db.Registries on c.Component equals r.Component_ + where r.Registry == "MyTestRegCompReg1" + select f.Feature; + + count = 0; + foreach (var featureName in features) + { + Console.WriteLine(featureName); + count++; + } + Assert.AreEqual(1, count); + + } + } + + [TestMethod] + public void EnumTable() + { + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create)) + { + this.InitLinqTestDatabase(db); + int count = 0; + foreach (var comp in db.Components) + { + Console.WriteLine(comp); + count++; + } + Assert.AreNotEqual(0, count); + } + } + + [TestMethod] + public void DatabaseAsQueryable() + { + using (Database db = new Database("testlinq.msi", DatabaseOpenMode.Create)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + var comps = from c in db.AsQueryable().Components + select c; + + int count = 0; + foreach (var c in comps) + { + Console.WriteLine(c); + count++; + } + + Assert.AreEqual(1, count); + } + } + + [TestMethod] + public void EnumProducts() + { + var products = from p in ProductInstallation.AllProducts + where p.Publisher == ".NET Foundation" + select new { Name = p.ProductName, + Ver = p.ProductVersion, + Company = p.Publisher, + InstallDate = p.InstallDate, + PackageCode = p.AdvertisedPackageCode }; + + foreach (var p in products) + { + Console.WriteLine(p); + Assert.IsTrue(p.Company == ".NET Foundation"); + } + } + + [TestMethod] + public void EnumFeatures() + { + foreach (var p in ProductInstallation.AllProducts) + { + Console.WriteLine(p.ProductName); + + foreach (var f in p.Features) + { + Console.WriteLine("\t" + f.FeatureName); + } + } + } + + [TestMethod] + public void EnumComponents() + { + var comps = from c in ComponentInstallation.AllComponents + where c.State == InstallState.Local && + c.Product.Publisher == ".NET Foundation" + select c.Path; + + int count = 0; + foreach (var c in comps) + { + if (++count == 100) break; + + Console.WriteLine(c); + } + + Assert.AreEqual(100, count); + } + } + +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj new file mode 100644 index 00000000..c34494b7 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj @@ -0,0 +1,42 @@ + + + + + {4F55F9B8-D8B6-41EB-8796-221B4CD98324} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.Linq + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + v4.7.2 + + + + + + + + + + + + + + + {85225597-5121-4361-8332-4E3246D5BBF5} + WixToolset.Dtf.WindowsInstaller + + + {7E66313B-C6D4-4729-8422-4D1474E0E6F7} + WixToolset.Dtf.WindowsInstaller.Linq + + + {16F5202F-9276-4166-975C-C9654BAF8012} + WixToolsetTests.Dtf.WindowsInstaller + + + + + + \ No newline at end of file diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs new file mode 100644 index 00000000..b0fc00a8 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs @@ -0,0 +1,173 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Reflection; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class EmbeddedExternalUI + { + const InstallLogModes TestLogModes = + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData; + +#if DEBUG + const string EmbeddedUISampleBinDir = @"..\..\build\debug\"; +#else + const string EmbeddedUISampleBinDir = @"..\..\build\release\"; +#endif + + [TestMethod] + [Ignore] // Requires elevation. + public void EmbeddedUISingleInstall() + { + string dbFile = "EmbeddedUISingleInstall.msi"; + string productCode; + + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + using (Record uiRec = new Record(5)) + { + uiRec[1] = "TestEmbeddedUI"; + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; + uiRec[3] = 1; + uiRec[4] = (int) ( + EmbeddedExternalUI.TestLogModes | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); + } + + db.Commit(); + } + + Installer.SetInternalUI(InstallUIOptions.Full); + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + Exception caughtEx = null; + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "install.log"); + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "uninstall.log"); + Installer.InstallProduct(dbFile, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while uninstalling product: " + caughtEx); + } + + // This test does not pass if run normally. + // It only passes when a failure is injected into the EmbeddedUI launcher. + ////[TestMethod] + public void EmbeddedUIInitializeFails() + { + string dbFile = "EmbeddedUIInitializeFails.msi"; + string productCode; + + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir); + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll"; + + // A number that will be used to check whether a type 19 CA runs. + const string magicNumber = "3.14159265358979323846264338327950"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + const string failureActionName = "EmbeddedUIInitializeFails"; + db.Execute("INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) " + + "VALUES ('{0}', 19, '', 'Logging magic number: {1}')", failureActionName, magicNumber); + + // This type 19 CA (launch condition) is given a condition of 'UILevel = 3' so that it only runs if the + // installation is running in BASIC UI mode, which is what we expect if the EmbeddedUI fails to initialize. + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) " + + "VALUES ('{0}', 'UILevel = 3', 1)", failureActionName); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + using (Record uiRec = new Record(5)) + { + uiRec[1] = "TestEmbeddedUI"; + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll"; + uiRec[3] = 1; + uiRec[4] = (int)( + EmbeddedExternalUI.TestLogModes | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + uiRec.SetStream(5, Path.Combine(uiDir, uiFile)); + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec); + } + + db.Commit(); + } + + Installer.SetInternalUI(InstallUIOptions.Full); + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + string logFile = "install.log"; + Exception caughtEx = null; + try + { + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, logFile); + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsInstanceOfType(caughtEx, typeof(InstallerException), + "Excpected InstallerException installing product; caught: " + caughtEx); + + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed."); + + string logText = File.ReadAllText(logFile); + Assert.IsTrue(logText.Contains(magicNumber), "Checking that the type 19 custom action ran."); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs new file mode 100644 index 00000000..26c172c9 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs @@ -0,0 +1,238 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Text; + using WixToolset.Dtf.WindowsInstaller; + + public static class Schema + { + public static IList Tables + { + get + { + return new TableInfo[] + { + Binary, + Component, + CustomAction, + Directory, + EmbeddedUI, + Feature, + FeatureComponents, + File, + InstallExecuteSequence, + Media, + Property, + Registry + }; + } + } + + #region Table data + + public static TableInfo Binary { get { return new TableInfo( + "Binary", + new ColumnInfo[] + { + new ColumnInfo("Name", typeof(String), 72, true), + new ColumnInfo("Data", typeof(Stream), 0, true), + }, + new string[] { "Name" }); + } } + + public static TableInfo Component { get { return new TableInfo( + "Component", + new ColumnInfo[] + { + new ColumnInfo("Component", typeof(String), 72, true), + new ColumnInfo("ComponentId", typeof(String), 38, false), + new ColumnInfo("Directory_", typeof(String), 72, true), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + new ColumnInfo("Condition", typeof(String), 255, false), + new ColumnInfo("KeyPath", typeof(String), 72, false), + }, + new string[] { "Component" }); + } } + + public static TableInfo CustomAction { get { return new TableInfo( + "CustomAction", + new ColumnInfo[] + { + new ColumnInfo("Action", typeof(String), 72, true), + new ColumnInfo("Type", typeof(Int16), 2, true), + new ColumnInfo("Source", typeof(String), 64, false), + new ColumnInfo("Target", typeof(String), 255, false), + }, + new string[] { "Action" }); + } } + + public static TableInfo Directory { get { return new TableInfo( + "Directory", + new ColumnInfo[] + { + new ColumnInfo("Directory", typeof(String), 72, true), + new ColumnInfo("Directory_Parent", typeof(String), 72, false), + new ColumnInfo("DefaultDir", typeof(String), 255, true, false, true), + }, + new string[] { "Directory" }); + } } + + public static TableInfo EmbeddedUI { get { return new TableInfo( + "MsiEmbeddedUI", + new ColumnInfo[] + { + new ColumnInfo("MsiEmbeddedUI", typeof(String), 72, true), + new ColumnInfo("FileName", typeof(String), 72, true), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + new ColumnInfo("MessageFilter", typeof(Int32), 4, false), + new ColumnInfo("Data", typeof(Stream), 0, true), + }, + new string[] { "MsiEmbeddedUI" }); + } } + + public static TableInfo Feature { get { return new TableInfo( + "Feature", + new ColumnInfo[] + { + new ColumnInfo("Feature", typeof(String), 38, true), + new ColumnInfo("Feature_Parent", typeof(String), 38, false), + new ColumnInfo("Title", typeof(String), 64, false, false, true), + new ColumnInfo("Description", typeof(String), 64, false, false, true), + new ColumnInfo("Display", typeof(Int16), 2, false), + new ColumnInfo("Level", typeof(Int16), 2, true), + new ColumnInfo("Directory_", typeof(String), 72, false), + new ColumnInfo("Attributes", typeof(Int16), 2, true), + }, + new string[] { "Feature" }); + } } + + public static TableInfo FeatureComponents { get { return new TableInfo( + "FeatureComponents", + new ColumnInfo[] + { + new ColumnInfo("Feature_", typeof(String), 38, true), + new ColumnInfo("Component_", typeof(String), 72, true), + }, + new string[] { "Feature_", "Component_" }); + } } + + public static TableInfo File { get { return new TableInfo( + "File", + new ColumnInfo[] + { + new ColumnInfo("File", typeof(String), 72, true), + new ColumnInfo("Component_", typeof(String), 72, true), + new ColumnInfo("FileName", typeof(String), 255, true, false, true), + new ColumnInfo("FileSize", typeof(Int32), 4, true), + new ColumnInfo("Version", typeof(String), 72, false), + new ColumnInfo("Language", typeof(String), 20, false), + new ColumnInfo("Attributes", typeof(Int16), 2, false), + new ColumnInfo("Sequence", typeof(Int16), 2, true), + }, + new string[] { "File" }); + } } + + public static TableInfo InstallExecuteSequence { get { return new TableInfo( + "InstallExecuteSequence", + new ColumnInfo[] + { + new ColumnInfo("Action", typeof(String), 72, true), + new ColumnInfo("Condition", typeof(String), 255, false), + new ColumnInfo("Sequence", typeof(Int16), 2, true), + }, + new string[] { "Action" }); + } } + + public static TableInfo Media { get { return new TableInfo( + "Media", + new ColumnInfo[] + { + new ColumnInfo("DiskId", typeof(Int16), 2, true), + new ColumnInfo("LastSequence", typeof(Int16), 2, true), + new ColumnInfo("DiskPrompt", typeof(String), 64, false, false, true), + new ColumnInfo("Cabinet", typeof(String), 255, false), + new ColumnInfo("VolumeLabel", typeof(String), 32, false), + new ColumnInfo("Source", typeof(String), 32, false), + }, + new string[] { "DiskId" }); + } } + + public static TableInfo Property { get { return new TableInfo( + "Property", + new ColumnInfo[] + { + new ColumnInfo("Property", typeof(String), 72, true), + new ColumnInfo("Value", typeof(String), 255, true), + }, + new string[] { "Property" }); + } } + + public static TableInfo Registry { get { return new TableInfo( + "Registry", + new ColumnInfo[] + { + new ColumnInfo("Registry", typeof(String), 72, true), + new ColumnInfo("Root", typeof(Int16), 2, true), + new ColumnInfo("Key", typeof(String), 255, true, false, true), + new ColumnInfo("Name", typeof(String), 255, false, false, true), + new ColumnInfo("Value", typeof(String), 0, false, false, true), + new ColumnInfo("Component_", typeof(String), 72, true), + }, + new string[] { "Registry" }); + } } + + #endregion + + } + + public class Action + { + public readonly string Name; + public readonly int Sequence; + + public Action(string name, int sequence) + { + this.Name = name; + this.Sequence = sequence; + } + + } + + public class Sequence + { + public static IList InstallExecute + { + get + { + return new Action[] + { + new Action("CostInitialize", 800), + new Action("FileCost", 900), + new Action("CostFinalize", 1000), + new Action("InstallValidate", 1400), + new Action("InstallInitialize", 1500), + new Action("ProcessComponents", 1600), + new Action("UnpublishComponents", 1700), + new Action("UnpublishFeatures", 1800), + new Action("RemoveRegistryValues", 2600), + new Action("RemoveFiles", 3500), + new Action("RemoveFolders", 3600), + new Action("CreateFolders", 3700), + new Action("MoveFiles", 3800), + new Action("InstallFiles", 4000), + new Action("WriteRegistryValues", 5000), + new Action("RegisterProduct", 6100), + new Action("PublishComponents", 6200), + new Action("PublishFeatures", 6300), + new Action("PublishProduct", 6400), + new Action("InstallFinalize", 6600), + }; + } + } + + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs new file mode 100644 index 00000000..f994dfef --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs @@ -0,0 +1,409 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class WindowsInstallerTest + { + public WindowsInstallerTest() + { + } + + [TestInitialize()] + public void Initialize() + { + } + + [TestCleanup()] + public void Cleanup() + { + } + + [TestMethod] + [Ignore] // Currently fails. + public void InstallerErrorMessages() + { + string msg3002 = Installer.GetErrorMessage(3002); + Console.WriteLine("3002=" + msg3002); + Assert.IsNotNull(msg3002); + Assert.IsTrue(msg3002.Length > 0); + } + + [TestMethod] + public void InstallerDatabaseSchema() + { + string dbFile = "InstallerDatabaseSchema.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + db.Commit(); + } + + Assert.IsTrue(File.Exists(dbFile), "Checking whether created database file " + dbFile + " exists."); + + using (Database db = new Database(dbFile, DatabaseOpenMode.ReadOnly)) + { + TableCollection tables = db.Tables; + Assert.AreEqual(Schema.Tables.Count, tables.Count, "Counting tables."); + Assert.AreEqual(Schema.Property.Columns.Count, tables["Property"].Columns.Count, "Counting columns in Property table."); + + foreach (TableInfo tableInfo in tables) + { + Console.WriteLine(tableInfo.Name); + foreach (ColumnInfo columnInfo in tableInfo.Columns) + { + Console.WriteLine("\t{0} {1}", columnInfo.Name, columnInfo.ColumnDefinitionString); + } + } + } + } + + [TestMethod] + public void InstallerViewTables() + { + string dbFile = "InstallerViewTables.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + db.Commit(); + + using (View view1 = db.OpenView("SELECT `Property`, `Value` FROM `Property` WHERE `Value` IS NOT NULL")) + { + IList viewTables = view1.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view2 = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES ('TestViewTables', 1)")) + { + IList viewTables = view2.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view3 = db.OpenView("UPDATE `Property` SET `Value` = 2 WHERE `Property` = 'TestViewTables'")) + { + IList viewTables = view3.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + + using (View view4 = db.OpenView("alter table Property hold")) + { + IList viewTables = view4.Tables; + Assert.IsNotNull(viewTables); + Assert.AreEqual(1, viewTables.Count); + Assert.AreEqual("Property", viewTables[0].Name); + } + } + } + + [TestMethod] + public void InstallerInstallProduct() + { + string dbFile = "InstallerInstallProduct.msi"; + string productCode; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db.Commit(); + } + + ProductInstallation installation = new ProductInstallation(productCode); + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting."); + + Installer.SetInternalUI(InstallUIOptions.Silent); + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); + + Exception caughtEx = null; + try + { + Installer.InstallProduct(dbFile, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); + + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed."); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); + + try + { + Installer.InstallProduct(dbFile, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx); + + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed after removing."); + + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); + } + + public static MessageResult ExternalUILogger( + InstallMessage messageType, + string message, + MessageButtons buttons, + MessageIcon icon, + MessageDefaultButton defaultButton) + { + Console.WriteLine("{0}: {1}", messageType, message); + return MessageResult.None; + } + + public static MessageResult ExternalUIRecordLogger( + InstallMessage messageType, + Record messageRecord, + MessageButtons buttons, + MessageIcon icon, + MessageDefaultButton defaultButton) + { + if (messageRecord != null) + { + if (messageRecord.FormatString.Length == 0 && messageRecord.FieldCount > 0) + { + messageRecord.FormatString = "1: [1] 2: [2] 3: [3] 4: [4] 5: [5]"; + } + Console.WriteLine("{0}: {1}", messageType, messageRecord.ToString()); + } + else + { + Console.WriteLine("{0}: (null)", messageType); + } + return MessageResult.None; + } + + [TestMethod] + [Ignore] // Currently fails. + public void InstallerMessageResources() + { + string message1101 = Installer.GetErrorMessage(1101); + Console.WriteLine("Message 1101: " + message1101); + Assert.IsNotNull(message1101); + Assert.IsTrue(message1101.Contains("file")); + + message1101 = Installer.GetErrorMessage(1101, CultureInfo.GetCultureInfo(1033)); + Console.WriteLine("Message 1101: " + message1101); + Assert.IsNotNull(message1101); + Assert.IsTrue(message1101.Contains("file")); + + string message2621 = Installer.GetErrorMessage(2621); + Console.WriteLine("Message 2621: " + message2621); + Assert.IsNotNull(message2621); + Assert.IsTrue(message2621.Contains("DLL")); + + string message3002 = Installer.GetErrorMessage(3002); + Console.WriteLine("Message 3002: " + message3002); + Assert.IsNotNull(message3002); + Assert.IsTrue(message3002.Contains("sequencing")); + } + + [TestMethod] + public void EnumComponentQualifiers() + { + foreach (ComponentInstallation comp in ComponentInstallation.AllComponents) + { + bool qualifiers = false; + foreach (ComponentInstallation.Qualifier qualifier in comp.Qualifiers) + { + if (!qualifiers) + { + Console.WriteLine(comp.Path); + qualifiers = true; + } + + Console.WriteLine("\t{0}: {1}", qualifier.QualifierCode, qualifier.Data); + } + } + } + + [TestMethod] + public void DeleteRecord() + { + string dbFile = "DeleteRecord.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string query = "SELECT `Property`, `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'"; + + using (View view = db.OpenView(query)) + { + view.Execute(); + + Record rec = view.Fetch(); + + Console.WriteLine("Calling ToString() : " + rec); + + view.Delete(rec); + } + + Assert.AreEqual(0, db.ExecuteStringQuery(query).Count); + } + } + + [TestMethod] + public void InsertRecordThenTryFormatString() + { + string dbFile = "InsertRecordThenTryFormatString.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string parameterFormatString = "[1]"; + string[] properties = new string[] + { + "SonGoku", "Over 9000", + }; + + string query = "SELECT `Property`, `Value` FROM `Property`"; + + using (View view = db.OpenView(query)) + { + using (Record rec = new Record(2)) + { + rec[1] = properties[0]; + rec[2] = properties[1]; + rec.FormatString = parameterFormatString; + Console.WriteLine("Format String before inserting: " + rec.FormatString); + view.Insert(rec); + + Console.WriteLine("Format String after inserting: " + rec.FormatString); + // After inserting, the format string is invalid. + Assert.AreEqual(String.Empty, rec.ToString()); + + // Setting the format string manually makes it valid again. + rec.FormatString = parameterFormatString; + Assert.AreEqual(properties[0], rec.ToString()); + } + } + } + } + + [TestMethod] + public void SeekRecordThenTryFormatString() + { + string dbFile = "SeekRecordThenTryFormatString.msi"; + + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db); + WindowsInstallerUtils.CreateTestProduct(db); + + string parameterFormatString = "[1]"; + string[] properties = new string[] + { + "SonGoku", "Over 9000", + }; + + string query = "SELECT `Property`, `Value` FROM `Property`"; + + using (View view = db.OpenView(query)) + { + using (Record rec = new Record(2)) + { + rec[1] = properties[0]; + rec[2] = properties[1]; + rec.FormatString = parameterFormatString; + Console.WriteLine("Record fields before seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); + view.Seek(rec); + + //TODO: Why does view.Seek remove the record fields? + Console.WriteLine("Record fields after seeking: " + rec[0] + " " + rec[1] + " " + rec[2]); + // After inserting, the format string is invalid. + Assert.AreEqual(String.Empty, rec.ToString()); + } + } + } + } + + [TestMethod] + public void TestToString() + { + string defaultString = "1: "; + string vegetaShout = "It's OVER 9000!!"; + string gokuPowerLevel = "9001"; + string nappaInquiry = "Vegeta, what's the Scouter say about his power level?"; + string parameterFormatString = "[1]"; + + Record rec = new Record(1); + Assert.AreEqual(defaultString, rec.ToString(), "Testing default FormatString"); + + rec.FormatString = String.Empty; + Assert.AreEqual(defaultString, rec.ToString(), "Explicitly set the FormatString to the empty string."); + + rec.FormatString = vegetaShout; + Assert.AreEqual(vegetaShout, rec.ToString(), "Testing text only (empty FormatString)"); + + rec.FormatString = gokuPowerLevel; + Assert.AreEqual(gokuPowerLevel, rec.ToString(), "Testing numbers only from a record that wasn't fetched."); + + Record rec2 = new Record(nappaInquiry); + rec2.FormatString = parameterFormatString; + Assert.AreEqual(nappaInquiry, rec2.ToString(), "Testing text with a FormatString set."); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs new file mode 100644 index 00000000..3bdf5acd --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs @@ -0,0 +1,161 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.IO; + using System.Windows.Forms; + using System.Globalization; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using WixToolset.Dtf.WindowsInstaller; + using View = WixToolset.Dtf.WindowsInstaller.View; + + [TestClass] + public class WindowsInstallerTransactions + { + [TestInitialize()] + public void Initialize() + { + } + + [TestCleanup()] + public void Cleanup() + { + } + + [TestMethod] + [Ignore] // Requires elevation. + public void InstallerTransactTwoProducts() + { + string dbFile1 = "InstallerTransactProduct1.msi"; + string dbFile2 = "InstallerTransactProduct2.msi"; + string productCode1; + string productCode2; + + using (Database db1 = new Database(dbFile1, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db1); + WindowsInstallerUtils.CreateTestProduct(db1); + + productCode1 = db1.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db1.Commit(); + } + + using (Database db2 = new Database(dbFile2, DatabaseOpenMode.CreateDirect)) + { + WindowsInstallerUtils.InitializeProductDatabase(db2); + WindowsInstallerUtils.CreateTestProduct(db2); + + productCode2 = db2.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0]; + + db2.Commit(); + } + + ProductInstallation installation1 = new ProductInstallation(productCode1); + ProductInstallation installation2 = new ProductInstallation(productCode2); + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed before starting."); + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed before starting."); + + Installer.SetInternalUI(InstallUIOptions.Silent); + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null."); + + Transaction transaction = new Transaction("TestInstallTransaction", TransactionAttributes.None); + + Exception caughtEx = null; + try + { + Installer.InstallProduct(dbFile1, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product 1: " + caughtEx); + + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + + try + { + Installer.InstallProduct(dbFile2, String.Empty); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while installing product 2: " + caughtEx); + + transaction.Commit(); + transaction.Close(); + + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned."); + + Assert.IsTrue(installation1.IsInstalled, "Checking that product 1 is installed."); + Assert.IsTrue(installation2.IsInstalled, "Checking that product 2 is installed."); + + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("==================================================================="); + Console.WriteLine("==================================================================="); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger, + InstallLogModes.FatalExit | + InstallLogModes.Error | + InstallLogModes.Warning | + InstallLogModes.User | + InstallLogModes.Info | + InstallLogModes.ResolveSource | + InstallLogModes.OutOfDiskSpace | + InstallLogModes.ActionStart | + InstallLogModes.ActionData | + InstallLogModes.CommonData | + InstallLogModes.Progress | + InstallLogModes.Initialize | + InstallLogModes.Terminate | + InstallLogModes.ShowDialog); + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null."); + + transaction = new Transaction("TestUninstallTransaction", TransactionAttributes.None); + + try + { + Installer.InstallProduct(dbFile1, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while removing product 1: " + caughtEx); + + try + { + Installer.InstallProduct(dbFile2, "REMOVE=All"); + } + catch (Exception ex) { caughtEx = ex; } + Assert.IsNull(caughtEx, "Exception thrown while removing product 2: " + caughtEx); + + transaction.Commit(); + transaction.Close(); + + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed after removing."); + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed after removing."); + + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None); + Assert.AreEqual(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned."); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs new file mode 100644 index 00000000..644f1988 --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs @@ -0,0 +1,174 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Test +{ + using System; + using System.Collections.Generic; + using System.Text; + using WixToolset.Dtf.WindowsInstaller; + + public class WindowsInstallerUtils + { + public static void InitializeProductDatabase(Database db) + { + InitializeProductDatabase(db, false); + } + + public static void InitializeProductDatabase(Database db, bool sixtyFourBit) + { + db.SummaryInfo.CodePage = (short) Encoding.Default.CodePage; + db.SummaryInfo.Title = "Windows Installer Test"; + db.SummaryInfo.Subject = db.SummaryInfo.Title; + db.SummaryInfo.Author = typeof(WindowsInstallerUtils).Assembly.FullName; + db.SummaryInfo.CreatingApp = db.SummaryInfo.Author; + db.SummaryInfo.Comments = typeof(WindowsInstallerUtils).FullName + ".CreateBasicDatabase()"; + db.SummaryInfo.Keywords = "Installer,MSI,Database"; + db.SummaryInfo.PageCount = 300; + db.SummaryInfo.WordCount = 0; + db.SummaryInfo.RevisionNumber = Guid.NewGuid().ToString("B").ToUpper(); + db.SummaryInfo.Template = (sixtyFourBit ? "x64" : "Intel") + ";0"; + + foreach (TableInfo tableInfo in Schema.Tables) + { + db.Execute(tableInfo.SqlCreateString); + } + + db.Execute("INSERT INTO `Directory` (`Directory`, `DefaultDir`) VALUES ('TARGETDIR', 'SourceDir')"); + db.Execute("INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES ('ProgramFilesFolder', 'TARGETDIR', '.')"); + + foreach (Action action in Sequence.InstallExecute) + { + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('{0}', {1})", + action.Name, action.Sequence); + } + } + + public const string UpgradeCode = "{05955FE8-005F-4695-A81F-D559338065BB}"; + + public static void CreateTestProduct(Database db) + { + Guid productGuid = Guid.NewGuid(); + + string[] properties = new string[] + { + "ProductCode", productGuid.ToString("B").ToUpper(), + "UpgradeCode", UpgradeCode, + "ProductName", "Windows Installer Test Product " + productGuid.ToString("P").ToUpper(), + "ProductVersion", "1.0.0.0000", + }; + + using (View view = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)")) + { + using (Record rec = new Record(2)) + { + for (int i = 0; i < properties.Length; i += 2) + { + rec[1] = properties[i]; + rec[2] = properties[i + 1]; + view.Execute(rec); + } + } + } + + int randomId = new Random().Next(10000); + string productDir = "TestDir" + randomId; + db.Execute( + "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) " + + "VALUES ('TestDir', 'ProgramFilesFolder', 'TestDir|{0}:.')", productDir); + + string compId = Guid.NewGuid().ToString("B").ToUpper(); + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + "TestRegComp1", + compId, + "TestDir", + (int) ComponentAttributes.RegistryKeyPath, + "TestReg1"); + + string productReg = "TestReg" + randomId; + db.Execute( + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}')", + "TestReg1", + -1, + @"Software\Microsoft\Windows Installer Test\" + productReg, + "TestRegComp1"); + + db.Execute( + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", + "TestFeature1", + "Test Feature 1", + 1, + (int) FeatureAttributes.None); + + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + "TestFeature1", + "TestRegComp1"); + } + + public static void AddFeature(Database db, string featureName) + { + db.Execute( + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})", + featureName, + featureName, + 1, + (int) FeatureAttributes.None); + } + + public static void AddRegistryComponent(Database db, + string featureName, string compName, string compId, + string keyName, string keyValueName, string value) + { + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + compName, + compId, + "TestDir", + (int) ComponentAttributes.RegistryKeyPath, + compName + "Reg1"); + db.Execute( + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}', '{4}', '{5}')", + compName + "Reg1", + -1, + @"Software\Microsoft\Windows Installer Test\" + keyName, + keyValueName, + value, + compName); + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + featureName, + compName); + } + + public static void AddFileComponent(Database db, + string featureName, string compName, string compId, + string fileKey, string fileName) + { + db.Execute( + "INSERT INTO `Component` " + + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " + + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')", + compName, + compId, + "TestDir", + (int) ComponentAttributes.None, + fileKey); + db.Execute( + "INSERT INTO `File` " + + "(`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) " + + "VALUES ('{0}', '{1}', '{2}', 1, 0, 1)", + fileKey, + compName, + fileName); + db.Execute( + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')", + featureName, + compName); + } + } +} diff --git a/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj new file mode 100644 index 00000000..eaa273ed --- /dev/null +++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj @@ -0,0 +1,39 @@ + + + + + {16F5202F-9276-4166-975C-C9654BAF8012} + Library + WixToolsetTests.Dtf + WixToolsetTests.Dtf.WindowsInstaller + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + false + v4.7.2 + + + + + + + + + + + + + + + + + + + + {85225597-5121-4361-8332-4E3246D5BBF5} + WixToolset.Dtf.WindowsInstaller + + + + + + diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index 09e14d52..6737cc38 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -2,6 +2,7 @@ + diff --git a/src/internal/WixBuildFinalize/WixBuildFinalize.proj b/src/internal/WixBuildFinalize/WixBuildFinalize.proj index 1e0a98d1..7453e324 100644 --- a/src/internal/WixBuildFinalize/WixBuildFinalize.proj +++ b/src/internal/WixBuildFinalize/WixBuildFinalize.proj @@ -3,7 +3,7 @@ - net46 + net472 diff --git a/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs deleted file mode 100644 index 7a2fa039..00000000 --- a/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyDescription("Sample managed embedded external UI")] diff --git a/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj deleted file mode 100644 index e4c52a26..00000000 --- a/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - - {864B8C50-7895-4485-AC89-900D86FD8C0D} - Library - WixToolset.Dtf.Samples.EmbeddedUI - WixToolset.Dtf.Samples.EmbeddedUI - v3.5 - 512 - - - - - - - SetupWizard.xaml - - - - - MSBuild:Compile - Designer - - - - - 3.0 - - - 3.0 - - - - 3.5 - - - - 3.0 - - - - - {24121677-0ed0-41b5-833f-1b9a18e87bf4} - WixToolset.Dtf.WindowsInstaller - - - - - - - diff --git a/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs deleted file mode 100644 index df77e106..00000000 --- a/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Samples.EmbeddedUI -{ - using System; - using WixToolset.Dtf.WindowsInstaller; - - /// - /// Tracks MSI progress messages and converts them to usable progress. - /// - public class InstallProgressCounter - { - private int total; - private int completed; - private int step; - private bool moveForward; - private bool enableActionData; - private int progressPhase; - private double scriptPhaseWeight; - - public InstallProgressCounter() : this(0.3) - { - } - - public InstallProgressCounter(double scriptPhaseWeight) - { - if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1)) - { - throw new ArgumentOutOfRangeException("scriptPhaseWeight"); - } - - this.scriptPhaseWeight = scriptPhaseWeight; - } - - /// - /// Gets a number between 0 and 1 that indicates the overall installation progress. - /// - public double Progress { get; private set; } - - public void ProcessMessage(InstallMessage messageType, Record messageRecord) - { - // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. - - switch (messageType) - { - case InstallMessage.ActionStart: - if (this.enableActionData) - { - this.enableActionData = false; - } - break; - - case InstallMessage.ActionData: - if (this.enableActionData) - { - if (this.moveForward) - { - this.completed += this.step; - } - else - { - this.completed -= this.step; - } - - this.UpdateProgress(); - } - break; - - case InstallMessage.Progress: - this.ProcessProgressMessage(messageRecord); - break; - } - } - - private void ProcessProgressMessage(Record progressRecord) - { - // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. - - if (progressRecord == null || progressRecord.FieldCount == 0) - { - return; - } - - int fieldCount = progressRecord.FieldCount; - int progressType = progressRecord.GetInteger(1); - string progressTypeString = String.Empty; - switch (progressType) - { - case 0: // Master progress reset - if (fieldCount < 4) - { - return; - } - - this.progressPhase++; - - this.total = progressRecord.GetInteger(2); - if (this.progressPhase == 1) - { - // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase - // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress - // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this - // "close" and deal with the rest. - this.total += 50; - } - - this.moveForward = (progressRecord.GetInteger(3) == 0); - this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max - this.enableActionData = false; - - this.UpdateProgress(); - break; - - case 1: // Action info - if (fieldCount < 3) - { - return; - } - - if (progressRecord.GetInteger(3) == 0) - { - this.enableActionData = false; - } - else - { - this.enableActionData = true; - this.step = progressRecord.GetInteger(2); - } - break; - - case 2: // Progress report - if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0) - { - return; - } - - if (this.moveForward) - { - this.completed += progressRecord.GetInteger(2); - } - else - { - this.completed -= progressRecord.GetInteger(2); - } - - this.UpdateProgress(); - break; - - case 3: // Progress total addition - this.total += progressRecord.GetInteger(2); - break; - } - } - - private void UpdateProgress() - { - if (this.progressPhase < 1 || this.total == 0) - { - this.Progress = 0; - } - else if (this.progressPhase == 1) - { - this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total; - } - else if (this.progressPhase == 2) - { - this.Progress = this.scriptPhaseWeight + - (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total; - } - else - { - this.Progress = 1; - } - } - } -} diff --git a/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs deleted file mode 100644 index 9b26bef5..00000000 --- a/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Samples.EmbeddedUI -{ - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Threading; - using System.Windows; - using System.Windows.Threading; - using WixToolset.Dtf.WindowsInstaller; - using Application = System.Windows.Application; - - public class SampleEmbeddedUI : IEmbeddedUI - { - private Thread appThread; - private Application app; - private SetupWizard setupWizard; - private ManualResetEvent installStartEvent; - private ManualResetEvent installExitEvent; - - /// - /// Initializes the embedded UI. - /// - /// Handle to the installer which can be used to get and set properties. - /// The handle is only valid for the duration of this method call. - /// Path to the directory that contains all the files from the MsiEmbeddedUI table. - /// On entry, contains the current UI level for the installation. After this - /// method returns, the installer resets the UI level to the returned value of this parameter. - /// True if the embedded UI was successfully initialized; false if the installation - /// should continue without the embedded UI. - /// The installation was canceled by the user. - /// The embedded UI failed to initialize and - /// causes the installation to fail. - public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel) - { - if (session != null) - { - if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full) - { - // Don't show custom UI when the UI level is set to basic. - return false; - - // An embedded UI could display an alternate dialog sequence for reduced or - // basic modes, but it's not implemented here. We'll just fall back to the - // built-in MSI basic UI. - } - - if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase)) - { - // Don't show custom UI when uninstalling. - return false; - - // An embedded UI could display an uninstall wizard, it's just not imlemented here. - } - } - - // Start the setup wizard on a separate thread. - this.installStartEvent = new ManualResetEvent(false); - this.installExitEvent = new ManualResetEvent(false); - this.appThread = new Thread(this.Run); - this.appThread.SetApartmentState(ApartmentState.STA); - this.appThread.Start(); - - // Wait for the setup wizard to either kickoff the install or prematurely exit. - int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent }); - if (waitResult == 1) - { - // The setup wizard set the exit event instead of the start event. Cancel the installation. - throw new InstallCanceledException(); - } - else - { - // Start the installation with a silenced internal UI. - // This "embedded external UI" will handle message types except for source resolution. - internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly; - return true; - } - } - - /// - /// Processes information and progress messages sent to the user interface. - /// - /// Message type. - /// Record that contains message data. - /// Message box buttons. - /// Message box icon. - /// Message box default button. - /// Result of processing the message. - public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, - MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) - { - // Synchronously send the message to the setup wizard window on its thread. - object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send, - new Func(delegate() - { - return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton); - })); - return (MessageResult) result; - } - - /// - /// Shuts down the embedded UI at the end of the installation. - /// - /// - /// If the installation was canceled during initialization, this method will not be called. - /// If the installation was canceled or failed at any later point, this method will be called at the end. - /// - public void Shutdown() - { - // Wait for the user to exit the setup wizard. - this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal, - new Action(delegate() - { - this.setupWizard.EnableExit(); - })); - this.appThread.Join(); - } - - /// - /// Creates the setup wizard and runs the application thread. - /// - private void Run() - { - this.app = new Application(); - this.setupWizard = new SetupWizard(this.installStartEvent); - this.setupWizard.InitializeComponent(); - this.app.Run(this.setupWizard); - this.installExitEvent.Set(); - } - } -} diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml deleted file mode 100644 index a43059e8..00000000 --- a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs deleted file mode 100644 index b25b8a9e..00000000 --- a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Samples.EmbeddedUI -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading; - using System.Windows; - using System.Windows.Controls; - using System.Windows.Data; - using System.Windows.Documents; - using System.Windows.Input; - using System.Windows.Media; - using System.Windows.Media.Imaging; - using System.Windows.Navigation; - using System.Windows.Shapes; - using WixToolset.Dtf.WindowsInstaller; - - /// - /// Interaction logic for SetupWizard.xaml - /// - public partial class SetupWizard : Window - { - private ManualResetEvent installStartEvent; - private InstallProgressCounter progressCounter; - private bool canceled; - - public SetupWizard(ManualResetEvent installStartEvent) - { - this.installStartEvent = installStartEvent; - this.progressCounter = new InstallProgressCounter(0.5); - } - - public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, - MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) - { - try - { - this.progressCounter.ProcessMessage(messageType, messageRecord); - this.progressBar.Value = this.progressBar.Minimum + - this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum); - this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%"; - - switch (messageType) - { - case InstallMessage.Error: - case InstallMessage.Warning: - case InstallMessage.Info: - string message = String.Format("{0}: {1}", messageType, messageRecord); - this.LogMessage(message); - break; - } - - if (this.canceled) - { - this.canceled = false; - return MessageResult.Cancel; - } - } - catch (Exception ex) - { - this.LogMessage(ex.ToString()); - this.LogMessage(ex.StackTrace); - } - - return MessageResult.OK; - } - - private void LogMessage(string message) - { - this.messagesTextBox.Text += Environment.NewLine + message; - this.messagesTextBox.ScrollToEnd(); - } - - internal void EnableExit() - { - this.progressBar.Visibility = Visibility.Hidden; - this.progressLabel.Visibility = Visibility.Hidden; - this.cancelButton.Visibility = Visibility.Hidden; - this.exitButton.Visibility = Visibility.Visible; - } - - private void installButton_Click(object sender, RoutedEventArgs e) - { - this.installButton.Visibility = Visibility.Hidden; - this.progressBar.Visibility = Visibility.Visible; - this.progressLabel.Visibility = Visibility.Visible; - this.installStartEvent.Set(); - } - - private void exitButton_Click(object sender, RoutedEventArgs e) - { - this.Close(); - } - - private void cancelButton_Click(object sender, RoutedEventArgs e) - { - if (this.installButton.Visibility == Visibility.Visible) - { - this.Close(); - } - else - { - this.canceled = true; - this.cancelButton.IsEnabled = false; - } - } - } -} diff --git a/src/samples/Dtf/ManagedCA/AssemblyInfo.cs b/src/samples/Dtf/ManagedCA/AssemblyInfo.cs deleted file mode 100644 index 75be36b2..00000000 --- a/src/samples/Dtf/ManagedCA/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyDescription("Sample managed custom actions")] diff --git a/src/samples/Dtf/ManagedCA/ManagedCA.csproj b/src/samples/Dtf/ManagedCA/ManagedCA.csproj deleted file mode 100644 index 7fb32ad4..00000000 --- a/src/samples/Dtf/ManagedCA/ManagedCA.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - {DB9E5F02-8241-440A-9B60-980EB5B42B13} - Library - WixToolset.Dtf.Samples.ManagedCA - WixToolset.Dtf.Samples.ManagedCA - v2.0 - OnBuildSuccess - - - - - - - - - - - {24121677-0ed0-41b5-833f-1b9a18e87bf4} - WixToolset.Dtf.WindowsInstaller - - - - - - - - diff --git a/src/samples/Dtf/ManagedCA/SampleCAs.cs b/src/samples/Dtf/ManagedCA/SampleCAs.cs deleted file mode 100644 index 645131c8..00000000 --- a/src/samples/Dtf/ManagedCA/SampleCAs.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Samples.ManagedCA -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Dtf.WindowsInstaller; - - public class SampleCAs - { - [CustomAction] - public static ActionResult SampleCA1(Session session) - { - using (Record msgRec = new Record(0)) - { - msgRec[0] = "Hello from SampleCA1!" + - "\r\nCLR version is v" + Environment.Version; - session.Message(InstallMessage.Info, msgRec); - session.Message(InstallMessage.User, msgRec); - } - - session.Log("Testing summary info..."); - SummaryInfo summInfo = session.Database.SummaryInfo; - session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber); - session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime); - - string testProp = session["SampleCATest"]; - session.Log("Simple property test: [SampleCATest]={0}.", testProp); - - session.Log("Testing subdirectory extraction..."); - string testFilePath = "testsub\\SampleCAs.cs"; - if (!File.Exists(testFilePath)) - { - session.Log("Subdirectory extraction failed. File not found: " + testFilePath); - return ActionResult.Failure; - } - else - { - session.Log("Found file extracted in subdirectory."); - } - - session.Log("Testing record stream extraction..."); - string tempFile = null; - try - { - tempFile = Path.GetTempFileName(); - using (View binView = session.Database.OpenView( - "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " + - "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " + - "`CustomAction`.`Source` = `Binary`.`Name`")) - { - binView.Execute(); - using (Record binRec = binView.Fetch()) - { - binRec.GetStream(1, tempFile); - } - } - - session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length); - string binFileVersion = Installer.GetFileVersion(tempFile); - session.Log("CA binary file version: {0}", binFileVersion); - } - finally - { - if (tempFile != null && File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } - - session.Log("Testing record stream reading..."); - using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'")) - { - binView2.Execute(); - using (Record binRec2 = binView2.Fetch()) - { - Stream stream = binRec2.GetStream("Data"); - string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd(); - session.Log("Test data: " + testData); - } - } - - session.Log("Listing components"); - using (View compView = session.Database.OpenView( - "SELECT `Component` FROM `Component`")) - { - compView.Execute(); - foreach (Record compRec in compView) - { - using (compRec) - { - session.Log("\t{0}", compRec["Component"]); - } - } - } - - session.Log("Testing the ability to access an external MSI database..."); - string tempDbFile = Path.GetTempFileName(); - using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect)) - { - // Just create an empty database. - } - using (Database tempDb2 = new Database(tempDbFile)) - { - // See if we can open and query the database. - IList tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); - session.Log("Found " + tables.Count + " tables in the newly created database."); - } - File.Delete(tempDbFile); - - return ActionResult.Success; - } - - [CustomAction("SampleCA2")] - public static ActionResult SampleCustomAction2(Session session) - { - using (Record msgRec = new Record(0)) - { - msgRec[0] = "Hello from SampleCA2!"; - session.Message(InstallMessage.Info, msgRec); - session.Message(InstallMessage.User, msgRec); - } - return ActionResult.UserExit; - } - } -} diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs deleted file mode 100644 index 76ff79b3..00000000 --- a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs +++ /dev/null @@ -1,711 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Dtf.Tools.MakeSfxCA -{ - using System; - using System.IO; - using System.Collections.Generic; - using System.Security; - using System.Text; - using System.Reflection; - using Compression; - using Compression.Cab; - using Resources; - using ResourceCollection = Resources.ResourceCollection; - - /// - /// Command-line tool for building self-extracting custom action packages. - /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's - /// entry-points and file version to look like the CA module. - /// - public static class MakeSfxCA - { - private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; - - private static TextWriter log; - - /// - /// Prints usage text for the tool. - /// - /// Console text writer. - public static void Usage(TextWriter w) - { - w.WriteLine("Deployment Tools Foundation custom action packager version {0}", - Assembly.GetExecutingAssembly().GetName().Version); - w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); - w.WriteLine(); - w.WriteLine("Usage: MakeSfxCA SfxCA.dll [support files ...]"); - w.WriteLine(); - w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); - w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); - w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); - } - - /// - /// Runs the MakeSfxCA command-line tool. - /// - /// Command-line arguments. - /// 0 on success, nonzero on failure. - public static int Main(string[] args) - { - if (args.Length < 3) - { - Usage(Console.Out); - return 1; - } - - var output = args[0]; - var sfxDll = args[1]; - var inputs = new string[args.Length - 2]; - Array.Copy(args, 2, inputs, 0, inputs.Length); - - try - { - Build(output, sfxDll, inputs, Console.Out); - return 0; - } - catch (ArgumentException ex) - { - Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); - return 1; - } - catch (FileNotFoundException ex) - { - Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); - return 1; - } - catch (Exception ex) - { - Console.Error.WriteLine("Error: Unexpected error: " + ex); - return 1; - } - } - - /// - /// Packages up all the inputs to the output location. - /// - /// Various exceptions are thrown - /// if things go wrong. - public static void Build(string output, string sfxDll, IList inputs, TextWriter log) - { - MakeSfxCA.log = log; - - if (string.IsNullOrEmpty(output)) - { - throw new ArgumentNullException("output"); - } - - if (string.IsNullOrEmpty(sfxDll)) - { - throw new ArgumentNullException("sfxDll"); - } - - if (inputs == null || inputs.Count == 0) - { - throw new ArgumentNullException("inputs"); - } - - if (!File.Exists(sfxDll)) - { - throw new FileNotFoundException(sfxDll); - } - - var customActionAssembly = inputs[0]; - if (!File.Exists(customActionAssembly)) - { - throw new FileNotFoundException(customActionAssembly); - } - - inputs = MakeSfxCA.SplitList(inputs); - - var inputsMap = MakeSfxCA.GetPackFileMap(inputs); - - var foundWIAssembly = false; - foreach (var input in inputsMap.Keys) - { - if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, - StringComparison.OrdinalIgnoreCase) == 0) - { - foundWIAssembly = true; - } - } - - if (!foundWIAssembly) - { - throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + - " must be included in the list of support files. " + - "If using the MSBuild targets, make sure the assembly reference " + - "has the Private (Copy Local) flag set."); - } - - MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); - - var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); - var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); - - if (entryPoints.Count == 0 && uiClass == null) - { - throw new ArgumentException( - "No CA or UI entry points found in module: " + customActionAssembly); - } - else if (entryPoints.Count > 0 && uiClass != null) - { - throw new NotSupportedException( - "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); - } - - var dir = Path.GetDirectoryName(output); - if (dir.Length > 0 && !Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - - using (Stream outputStream = File.Create(output)) - { - MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); - } - - MakeSfxCA.CopyVersionResource(customActionAssembly, output); - - MakeSfxCA.PackInputFiles(output, inputsMap); - - log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); - } - - /// - /// Splits any list items delimited by semicolons into separate items. - /// - /// Read-only input list. - /// New list with resulting split items. - private static IList SplitList(IList list) - { - var newList = new List(list.Count); - - foreach (var item in list) - { - if (!string.IsNullOrEmpty(item)) - { - foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) - { - newList.Add(splitItem); - } - } - } - - return newList; - } - - /// - /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. - /// - /// List of input files which include non-GAC dependent assemblies. - /// Directory to auto-locate additional dependent assemblies. - /// - /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them - /// to the list of input files if found. - /// - private static void ResolveDependentAssemblies(IDictionary inputFiles, string inputDir) - { - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) - { - AssemblyName resolveName = new AssemblyName(args.Name); - Assembly assembly = null; - - // First, try to find the assembly in the list of input files. - foreach (var inputFile in inputFiles.Values) - { - var inputName = Path.GetFileNameWithoutExtension(inputFile); - var inputExtension = Path.GetExtension(inputFile); - if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && - (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || - string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) - { - assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); - - if (assembly != null) - { - break; - } - } - } - - // Second, try to find the assembly in the input directory. - if (assembly == null && inputDir != null) - { - string assemblyPath = null; - if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) - { - assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; - } - else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) - { - assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; - } - - if (assemblyPath != null) - { - assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); - - if (assembly != null) - { - // Add this detected dependency to the list of files to be packed. - inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); - } - } - } - - // Third, try to load the assembly from the GAC. - if (assembly == null) - { - try - { - assembly = Assembly.ReflectionOnlyLoad(args.Name); - } - catch (FileNotFoundException) - { - } - } - - if (assembly != null) - { - if (string.Equals(assembly.GetName().ToString(), resolveName.ToString())) - { - log.WriteLine(" Loaded dependent assembly: " + assembly.Location); - return assembly; - } - - log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); - log.WriteLine(" Loaded assembly : " + assembly.GetName()); - log.WriteLine(" Reference assembly: " + resolveName); - } - else - { - log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); - } - - return null; - }; - } - - /// - /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. - /// - /// Path of the assembly file to laod. - /// Loaded assembly, or null if the load failed. - private static Assembly TryLoadDependentAssembly(string assemblyPath) - { - Assembly assembly = null; - try - { - assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); - } - catch (IOException ex) - { - log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); - } - catch (BadImageFormatException ex) - { - log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); - } - catch (SecurityException ex) - { - log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); - } - - return assembly; - } - - /// - /// Searches the types in the input assembly for a type that implements IEmbeddedUI. - /// - /// - /// - private static string FindEmbeddedUIClass(string module) - { - log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); - - string uiClass = null; - - var assembly = Assembly.ReflectionOnlyLoadFrom(module); - - foreach (var type in assembly.GetExportedTypes()) - { - if (!type.IsAbstract) - { - foreach (var interfaceType in type.GetInterfaces()) - { - if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") - { - if (uiClass == null) - { - uiClass = assembly.GetName().Name + "!" + type.FullName; - } - else - { - throw new ArgumentException("Multiple IEmbeddedUI implementations found."); - } - } - } - } - } - - return uiClass; - } - - /// - /// Reflects on an input CA module to locate custom action entry-points. - /// - /// Assembly module with CA entry-points. - /// Mapping from entry-point names to assembly!class.method paths. - private static IDictionary FindEntryPoints(string module) - { - log.WriteLine("Searching for custom action entry points " + - "in {0}", Path.GetFileName(module)); - - var entryPoints = new Dictionary(); - - var assembly = Assembly.ReflectionOnlyLoadFrom(module); - - foreach (var type in assembly.GetExportedTypes()) - { - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) - { - var entryPointName = MakeSfxCA.GetEntryPoint(method); - if (entryPointName != null) - { - var entryPointPath = string.Format( - "{0}!{1}.{2}", - Path.GetFileNameWithoutExtension(module), - type.FullName, - method.Name); - entryPoints.Add(entryPointName, entryPointPath); - - log.WriteLine(" {0}={1}", entryPointName, entryPointPath); - } - } - } - - return entryPoints; - } - - /// - /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. - /// - /// A public static method. - /// Entrypoint name for the method as specified by the custom action attribute or just the method name, - /// or null if the method is not a custom action method. - private static string GetEntryPoint(MethodInfo method) - { - IList attributes; - try - { - attributes = CustomAttributeData.GetCustomAttributes(method); - } - catch (FileLoadException) - { - // Already logged load failures in the assembly-resolve-handler. - return null; - } - - foreach (CustomAttributeData attribute in attributes) - { - if (attribute.ToString().StartsWith( - "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", - StringComparison.Ordinal)) - { - string entryPointName = null; - foreach (var argument in attribute.ConstructorArguments) - { - // The entry point name is the first positional argument, if specified. - entryPointName = (string) argument.Value; - break; - } - - if (string.IsNullOrEmpty(entryPointName)) - { - entryPointName = method.Name; - } - - return entryPointName; - } - } - - return null; - } - - /// - /// Counts the number of template entrypoints in SfxCA.dll. - /// - /// - /// Depending on the requirements, SfxCA.dll might be built with - /// more entrypoints than the default. - /// - private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) - { - for (var count = 0; ; count++) - { - var templateName = string.Format(entryPointFormat, count); - var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); - - var nameOffset = FindBytes(fileBytes, templateAsciiBytes); - if (nameOffset < 0) - { - return count; - } - } - } - - /// - /// Writes a modified version of SfxCA.dll to the output stream, - /// with the template entry-points mapped to the CA entry-points. - /// - /// - /// To avoid having to recompile SfxCA.dll for every different set of CAs, - /// this method looks for a preset number of template entry-points in the - /// binary file and overwrites their entrypoint name and string data with - /// CA-specific values. - /// - private static void WriteEntryModule( - string sfxDll, Stream outputStream, IDictionary entryPoints, string uiClass) - { - log.WriteLine("Modifying SfxCA.dll stub"); - - byte[] fileBytes; - using (var readStream = File.OpenRead(sfxDll)) - { - fileBytes = new byte[(int) readStream.Length]; - readStream.Read(fileBytes, 0, fileBytes.Length); - } - - const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; - const int MAX_ENTRYPOINT_NAME = 72; - const int MAX_ENTRYPOINT_PATH = 160; - //var emptyBytes = new byte[0]; - - var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); - - if (slotCount == 0) - { - throw new ArgumentException("Invalid SfxCA.dll file."); - } - - if (entryPoints.Count > slotCount) - { - throw new ArgumentException(string.Format( - "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + - "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", - entryPoints.Count, slotCount)); - } - - var slotSort = new string[slotCount]; - for (var i = 0; i < slotCount - entryPoints.Count; i++) - { - slotSort[i] = string.Empty; - } - - entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); - Array.Sort(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); - - for (var i = 0; ; i++) - { - var templateName = string.Format(ENTRYPOINT_FORMAT, i); - var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); - var templateUniBytes = Encoding.Unicode.GetBytes(templateName); - - var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); - if (nameOffset < 0) - { - break; - } - - var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); - if (pathOffset < 0) - { - break; - } - - var entryPointName = slotSort[i]; - var entryPointPath = entryPointName.Length > 0 ? - entryPoints[entryPointName] : string.Empty; - - if (entryPointName.Length > MAX_ENTRYPOINT_NAME) - { - throw new ArgumentException(string.Format( - "Entry point name exceeds limit of {0} characters: {1}", - MAX_ENTRYPOINT_NAME, - entryPointName)); - } - - if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) - { - throw new ArgumentException(string.Format( - "Entry point path exceeds limit of {0} characters: {1}", - MAX_ENTRYPOINT_PATH, - entryPointPath)); - } - - var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); - var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); - - MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); - MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); - } - - if (entryPoints.Count == 0 && uiClass != null) - { - // Remove the zzz prefix from exported EmbeddedUI entry-points. - foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) - { - var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); - - var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); - if (exportOffset < 0) - { - throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); - } - - var replaceNameBytes = Encoding.ASCII.GetBytes(export); - MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); - } - - if (uiClass.Length > MAX_ENTRYPOINT_PATH) - { - throw new ArgumentException(string.Format( - "UI class full name exceeds limit of {0} characters: {1}", - MAX_ENTRYPOINT_PATH, - uiClass)); - } - - var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); - var replaceBytes = Encoding.Unicode.GetBytes(uiClass); - - // Fill in the embedded UI implementor class so the proxy knows which one to load. - var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); - if (replaceOffset >= 0) - { - MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); - } - } - - outputStream.Write(fileBytes, 0, fileBytes.Length); - } - - /// - /// Searches for a sub-array of bytes within a larger array of bytes. - /// - private static int FindBytes(byte[] source, byte[] find) - { - for (var i = 0; i < source.Length; i++) - { - int j; - for (j = 0; j < find.Length; j++) - { - if (source[i + j] != find[j]) - { - break; - } - } - - if (j == find.Length) - { - return i; - } - } - - return -1; - } - - /// - /// Replaces a range of bytes with new bytes, padding any extra part - /// of the range with zeroes. - /// - private static void ReplaceBytes( - byte[] source, int offset, int length, byte[] replace) - { - for (var i = 0; i < length; i++) - { - if (i < replace.Length) - { - source[offset + i] = replace[i]; - } - else - { - source[offset + i] = 0; - } - } - } - - /// - /// Print the name of one file as it is being packed into the cab. - /// - private static void PackProgress(object source, ArchiveProgressEventArgs e) - { - if (e.ProgressType == ArchiveProgressType.StartFile && log != null) - { - log.WriteLine(" {0}", e.CurrentFileName); - } - } - - /// - /// Gets a mapping from filenames as they will be in the cab to filenames - /// as they are currently on disk. - /// - /// - /// By default, all files will be placed in the root of the cab. But inputs may - /// optionally include an alternate inside-cab file path before an equals sign. - /// - private static IDictionary GetPackFileMap(IList inputs) - { - var fileMap = new Dictionary(); - foreach (var inputFile in inputs) - { - if (inputFile.IndexOf('=') > 0) - { - var parse = inputFile.Split('='); - if (!fileMap.ContainsKey(parse[0])) - { - fileMap.Add(parse[0], parse[1]); - } - } - else - { - var fileName = Path.GetFileName(inputFile); - if (!fileMap.ContainsKey(fileName)) - { - fileMap.Add(fileName, inputFile); - } - } - } - return fileMap; - } - - /// - /// Packs the input files into a cab that is appended to the - /// output SfxCA.dll. - /// - private static void PackInputFiles(string outputFile, IDictionary fileMap) - { - log.WriteLine("Packaging files"); - - var cabInfo = new CabInfo(outputFile); - cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); - } - - /// - /// Copies the version resource information from the CA module to - /// the CA package. This gives the package the file version and - /// description of the CA module, instead of the version and - /// description of SfxCA.dll. - /// - private static void CopyVersionResource(string sourceFile, string destFile) - { - log.WriteLine("Copying file version info from {0} to {1}", - sourceFile, destFile); - - var rc = new ResourceCollection(); - rc.Find(sourceFile, ResourceType.Version); - rc.Load(sourceFile); - rc.Save(destFile); - } - } -} diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj deleted file mode 100644 index 25b2cdb8..00000000 --- a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - netcoreapp3.1;net472 - Exe - WixToolset.Dtf.Tools.MakeSfxCA - MakeSfxCA - embedded - app.config - MakeSfxCA.exe.manifest - Major - win-x86 - - - - - - - - - - - - - diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest deleted file mode 100644 index 49b074e0..00000000 --- a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - WiX Toolset Compiler - - - - - - - - - - true - - - diff --git a/src/samples/Dtf/Tools/MakeSfxCA/app.config b/src/samples/Dtf/Tools/MakeSfxCA/app.config deleted file mode 100644 index 65d3d6c3..00000000 --- a/src/samples/Dtf/Tools/MakeSfxCA/app.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp deleted file mode 100644 index 1988fb2a..00000000 --- a/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" - -void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); - -//--------------------------------------------------------------------- -// CLR HOSTING -//--------------------------------------------------------------------- - -/// -/// Binds to the CLR after determining the appropriate version. -/// -/// Handle to the installer session, -/// used just for logging. -/// Specific version of the CLR to load. -/// If null, then the config file and/or primary assembly are -/// used to determine the version. -/// XML .config file which may contain -/// a startup section to direct which version of the CLR to use. -/// May be NULL. -/// Assembly to be used to determine -/// the version of the CLR in the absence of other configuration. -/// May be NULL. -/// Returned runtime host interface. -/// True if the CLR was loaded successfully, false if -/// there was some error. -/// -/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or -/// does not contain any version configuration, the CLR will not be loaded. -/// -bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, - const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost) -{ - typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, - LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, - LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, - LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); - typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, - DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv); - - HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll"); - if (hmodMscoree == NULL) - { - Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action " - L"requires the .NET Framework to be installed.", GetLastError()); - return false; - } - PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo) - GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo"); - PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx) - GetProcAddress(hmodMscoree, "CorBindToRuntimeEx"); - if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL) - { - Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action " - L"requires the .NET Framework to be installed.", GetLastError()); - FreeLibrary(hmodMscoree); - return false; - } - - wchar_t szClrVersion[20]; - HRESULT hr; - - if (szVersion != NULL && szVersion[0] != L'\0') - { - wcsncpy_s(szClrVersion, 20, szVersion, 20); - } - else - { - wchar_t szVersionDir[MAX_PATH]; - hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL, - szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL); - if (FAILED(hr)) - { - Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr); - Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or " - L"that there is a matching supportedRuntime element in CustomAction.config. " - L"If you are binding to .NET 4 or greater add " - L"useLegacyV2RuntimeActivationPolicy=true to the element."); - FreeLibrary(hmodMscoree); - return false; - } - } - - Log(hSession, L"Binding to CLR version %s", szClrVersion); - - ICorRuntimeHost* pHost; - hr = pCorBindToRuntimeEx(szClrVersion, NULL, - STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, - CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost); - if (FAILED(hr)) - { - Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr); - FreeLibrary(hmodMscoree); - return false; - } - hr = pHost->Start(); - if (FAILED(hr)) - { - Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr); - pHost->Release(); - FreeLibrary(hmodMscoree); - return false; - } - *ppHost = pHost; - FreeLibrary(hmodMscoree); - return true; -} - -/// -/// Creates a new CLR application domain. -/// -/// Handle to the installer session, -/// used just for logging -/// Interface to the runtime host where the -/// app domain will be created. -/// Name of the app domain to create. -/// Application base directory path, where -/// the app domain will look first to load its assemblies. -/// Optional XML .config file containing any -/// configuration for thae app domain. -/// Returned app domain interface. -/// True if the app domain was created successfully, false if -/// there was some error. -bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, - const wchar_t* szName, const wchar_t* szAppBase, - const wchar_t* szConfigFile, _AppDomain** ppAppDomain) -{ - IUnknown* punkAppDomainSetup = NULL; - IAppDomainSetup* pAppDomainSetup = NULL; - HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup); - if (SUCCEEDED(hr)) - { - hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup); - punkAppDomainSetup->Release(); - } - if (FAILED(hr)) - { - Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr); - return false; - } - - const wchar_t* szUrlPrefix = L"file:///"; - size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase); - wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t)); - if (szApplicationBase == NULL) hr = E_OUTOFMEMORY; - else - { - StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix); - StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase); - BSTR bstrApplicationBase = SysAllocString(szApplicationBase); - if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY; - else - { - hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase); - SysFreeString(bstrApplicationBase); - } - } - - if (SUCCEEDED(hr) && szConfigFile != NULL) - { - BSTR bstrConfigFile = SysAllocString(szConfigFile); - if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY; - else - { - hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile); - SysFreeString(bstrConfigFile); - } - } - - if (FAILED(hr)) - { - Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr); - pAppDomainSetup->Release(); - return false; - } - - IUnknown* punkAppDomain; - hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain); - pAppDomainSetup->Release(); - if (SUCCEEDED(hr)) - { - hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain); - punkAppDomain->Release(); - } - - if (FAILED(hr)) - { - Log(hSession, L"Failed to create app domain. Error code 0x%X", hr); - return false; - } - - return true; -} - -/// -/// Locates a specific method in a specific class and assembly. -/// -/// Handle to the installer session, -/// used just for logging -/// Application domain in which to -/// load assemblies. -/// Display name of the assembly -/// containing the method. -/// Fully-qualified name of the class -/// containing the method. -/// Name of the method. -/// Returned method interface. -/// True if the method was located, otherwise false. -/// Only public static methods are searched. Method -/// parameter types are not considered; if there are multiple -/// matching methods with different parameters, an error results. -bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, - const wchar_t* szAssembly, const wchar_t* szClass, - const wchar_t* szMethod, _MethodInfo** ppMethod) -{ - HRESULT hr; - _Assembly* pAssembly = NULL; - BSTR bstrAssemblyName = SysAllocString(szAssembly); - if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY; - else - { - hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly); - SysFreeString(bstrAssemblyName); - } - if (FAILED(hr)) - { - Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr); - return false; - } - - _Type* pType = NULL; - BSTR bstrClass = SysAllocString(szClass); - if (bstrClass == NULL) hr = E_OUTOFMEMORY; - else - { - hr = pAssembly->GetType_2(bstrClass, &pType); - SysFreeString(bstrClass); - } - pAssembly->Release(); - if (FAILED(hr) || pType == NULL) - { - Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr); - return false; - } - - BSTR bstrMethod = SysAllocString(szMethod); - if (bstrMethod == NULL) hr = E_OUTOFMEMORY; - else - { - hr = pType->GetMethod_2(bstrMethod, - (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod); - SysFreeString(bstrMethod); - } - pType->Release(); - if (FAILED(hr) || *ppMethod == NULL) - { - Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr); - return false; - } - return true; -} diff --git a/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp deleted file mode 100644 index a49cdeec..00000000 --- a/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" -#include "SfxUtil.h" - -// Globals for keeping track of things across UI messages. -static const wchar_t* g_szWorkingDir; -static ICorRuntimeHost* g_pClrHost; -static _AppDomain* g_pAppDomain; -static _MethodInfo* g_pProcessMessageMethod; -static _MethodInfo* g_pShutdownMethod; - -// Reserve extra space for strings to be replaced at build time. -#define NULLSPACE \ -L"\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" \ -L"\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" \ -L"\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" \ -L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - -// Prototypes for local functions. -// See the function definitions for comments. - -bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, - const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); - -/// -/// First entry-point for the UI DLL when loaded and called by MSI. -/// Extracts the payload, hosts the CLR, and invokes the managed -/// initialize method. -/// -/// Handle to the installer session, -/// used for logging errors and to be passed on to the managed initialize method. -/// Path the directory where resources from the MsiEmbeddedUI table -/// have been extracted, and where additional payload from this package will be extracted. -/// MSI install UI level passed to and returned from -/// the managed initialize method. -extern "C" -UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) -{ - // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. - UINT uiResult = INSTALLUILEVEL_BASIC; - - const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; - - g_szWorkingDir = szResourcePath; - - wchar_t szModule[MAX_PATH]; - DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); - if (cchCopied == 0) - { - Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); - return uiResult; - } - else if (cchCopied == MAX_PATH - 1) - { - Log(hSession, L"Failed to get module path -- path is too long."); - return uiResult; - } - - Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); - int err = ExtractCabinet(szModule, g_szWorkingDir); - if (err != 0) - { - Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); - Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " - L"any file contained in the embedded UI package."); - return uiResult; - } - - wchar_t szConfigFilePath[MAX_PATH + 20]; - StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); - StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); - - const wchar_t* szConfigFile = szConfigFilePath; - if (!PathFileExists(szConfigFilePath)) - { - szConfigFile = NULL; - } - - wchar_t szWIAssembly[MAX_PATH + 50]; - StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); - StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); - - if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) - { - if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, - szConfigFile, &g_pAppDomain)) - { - const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; - const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; - const wchar_t* szInitMethod = L"Initialize"; - const wchar_t* szProcessMessageMethod = L"ProcessMessage"; - const wchar_t* szShutdownMethod = L"Shutdown"; - - if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, - szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && - GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, - szProxyClass, szShutdownMethod, &g_pShutdownMethod)) - { - _MethodInfo* pInitMethod; - if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, - szProxyClass, szInitMethod, &pInitMethod)) - { - bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); - pInitMethod->Release(); - if (invokeSuccess) - { - if (uiResult == 0) - { - return ERROR_SUCCESS; - } - else if (uiResult == ERROR_INSTALL_USEREXIT) - { - // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. - // So return success here and then IDCANCEL on the next progress message. - uiResult = 0; - *pdwInternalUILevel = INSTALLUILEVEL_NONE; - Log(hSession, L"Initialization canceled by user."); - } - } - } - } - - g_pProcessMessageMethod->Release(); - g_pProcessMessageMethod = NULL; - g_pShutdownMethod->Release(); - g_pShutdownMethod = NULL; - - g_pClrHost->UnloadDomain(g_pAppDomain); - g_pAppDomain->Release(); - g_pAppDomain = NULL; - } - g_pClrHost->Stop(); - g_pClrHost->Release(); - g_pClrHost = NULL; - } - - return uiResult; -} - -/// -/// Entry-point for UI progress messages received from the MSI engine during an active installation. -/// Forwards the progress messages to the managed handler method and returns its result. -/// -extern "C" -INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) -{ - if (g_pProcessMessageMethod == NULL) - { - // Initialization was canceled. - return IDCANCEL; - } - - VARIANT vResult; - VariantInit(&vResult); - - VARIANT vNull; - vNull.vt = VT_EMPTY; - - SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); - VARIANT vMessageType; - vMessageType.vt = VT_I4; - vMessageType.lVal = (LONG) uiMessageType; - LONG index = 0; - HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); - if (FAILED(hr)) goto LExit; - VARIANT vRecord; - vRecord.vt = VT_I4; - vRecord.lVal = (LONG) hRecord; - index = 1; - hr = SafeArrayPutElement(saArgs, &index, &vRecord); - if (FAILED(hr)) goto LExit; - - hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); - -LExit: - SafeArrayDestroy(saArgs); - if (SUCCEEDED(hr)) - { - return vResult.intVal; - } - else - { - return -1; - } -} - -/// -/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. -/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. -/// -extern "C" -DWORD __stdcall ShutdownEmbeddedUI() -{ - if (g_pShutdownMethod != NULL) - { - VARIANT vNull; - vNull.vt = VT_EMPTY; - SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); - g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); - SafeArrayDestroy(saArgs); - - g_pClrHost->UnloadDomain(g_pAppDomain); - g_pAppDomain->Release(); - g_pClrHost->Stop(); - g_pClrHost->Release(); - } - - return 0; -} - -/// -/// Loads and invokes the managed portion of the proxy. -/// -/// Managed initialize method to be invoked. -/// Handle to the installer session, -/// used for logging errors and to be passed on to the managed initialize method. -/// Name of the UI class to be loaded. -/// This must be of the form: AssemblyName!Namespace.Class -/// MSI install UI level passed to and returned from -/// the managed initialize method. -/// Return value of the invoked initialize method. -/// True if the managed proxy was invoked successfully, or an -/// error code if there was some error. Note the initialize method itself may -/// return an error via puiResult while this method still returns true -/// since the invocation was successful. -bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) -{ - VARIANT vResult; - VariantInit(&vResult); - - VARIANT vNull; - vNull.vt = VT_EMPTY; - - SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); - VARIANT vSessionHandle; - vSessionHandle.vt = VT_I4; - vSessionHandle.lVal = (LONG) hSession; - LONG index = 0; - HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); - if (FAILED(hr)) goto LExit; - VARIANT vEntryPoint; - vEntryPoint.vt = VT_BSTR; - vEntryPoint.bstrVal = SysAllocString(szClassName); - if (vEntryPoint.bstrVal == NULL) - { - hr = E_OUTOFMEMORY; - goto LExit; - } - index = 1; - hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); - if (FAILED(hr)) goto LExit; - VARIANT vUILevel; - vUILevel.vt = VT_I4; - vUILevel.ulVal = *pdwInternalUILevel; - index = 2; - hr = SafeArrayPutElement(saArgs, &index, &vUILevel); - if (FAILED(hr)) goto LExit; - - hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); - -LExit: - SafeArrayDestroy(saArgs); - if (SUCCEEDED(hr)) - { - *puiResult = (UINT) vResult.lVal; - if ((*puiResult & 0xFFFF) == 0) - { - // Due to interop limitations, the successful resulting UILevel is returned - // as the high-word of the return value instead of via a ref parameter. - *pdwInternalUILevel = *puiResult >> 16; - *puiResult = 0; - } - return true; - } - else - { - Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); - return false; - } -} diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.def b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def deleted file mode 100644 index dd28b920..00000000 --- a/src/samples/Dtf/Tools/SfxCA/EntryPoints.def +++ /dev/null @@ -1,140 +0,0 @@ -; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - - -LIBRARY "SfxCA" - -EXPORTS - -CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 -CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 -CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 -CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 -CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 -CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 -CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 -CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 -CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 -CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 -CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 -CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 -CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 -CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 -CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 -CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 -CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 -CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 -CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 -CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 -CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 -CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 -CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 -CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 -CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 -CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 -CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 -CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 -CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 -CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 -CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 -CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 -CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 -CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 -CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 -CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 -CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 -CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 -CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 -CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 -CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 -CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 -CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 -CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 -CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 -CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 -CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 -CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 -CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 -CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 -CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 -CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 -CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 -CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 -CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 -CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 -CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 -CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 -CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 -CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 -CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 -CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 -CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 -CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 -CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 -CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 -CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 -CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 -CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 -CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 -CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 -CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 -CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 -CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 -CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 -CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 -CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 -CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 -CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 -CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 -CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 -CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 -CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 -CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 -CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 -CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 -CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 -CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 -CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 -CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 -CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 -CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 -CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 -CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 -CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 -CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 -CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 -CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 -CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 -CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 -CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 -CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 -CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 -CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 -CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 -CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 -CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 -CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 -CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 -CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 -CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 -CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 -CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 -CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 -CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 -CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 -CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 -CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 -CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 -CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 -CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 -CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 -CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 -CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 -CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 -CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 -CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 -CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 - -zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc -zzzInitializeEmbeddedUI=InitializeEmbeddedUI -zzzEmbeddedUIHandler=EmbeddedUIHandler -zzzShutdownEmbeddedUI=ShutdownEmbeddedUI diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.h b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h deleted file mode 100644 index bd2fa970..00000000 --- a/src/samples/Dtf/Tools/SfxCA/EntryPoints.h +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -int InvokeCustomAction(MSIHANDLE hSession, - const wchar_t* szWorkingDir, const wchar_t* szEntryPoint); - -/// -/// Macro for defining and exporting a custom action entrypoint. -/// -/// Name of the entrypoint as exported from -/// the DLL. -/// Path to the managed custom action method, -/// in the form: "AssemblyName!Namespace.Class.Method" -/// -/// To prevent the exported name from being decorated, add -/// /EXPORT:name to the linker options for every entrypoint. -/// -#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \ - name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); } - -// TEMPLATE ENTRYPOINTS -// To be edited by the MakeSfxCA tool. - -#define NULLSPACE \ -L"\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" \ -L"\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" \ -L"\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" \ -L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - -#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \ - CustomActionEntryPoint##id##, \ - L"CustomActionEntryPoint" sid NULLSPACE) - -TEMPLATE_CA_ENTRYPOINT(000,L"000"); -TEMPLATE_CA_ENTRYPOINT(001,L"001"); -TEMPLATE_CA_ENTRYPOINT(002,L"002"); -TEMPLATE_CA_ENTRYPOINT(003,L"003"); -TEMPLATE_CA_ENTRYPOINT(004,L"004"); -TEMPLATE_CA_ENTRYPOINT(005,L"005"); -TEMPLATE_CA_ENTRYPOINT(006,L"006"); -TEMPLATE_CA_ENTRYPOINT(007,L"007"); -TEMPLATE_CA_ENTRYPOINT(008,L"008"); -TEMPLATE_CA_ENTRYPOINT(009,L"009"); -TEMPLATE_CA_ENTRYPOINT(010,L"010"); -TEMPLATE_CA_ENTRYPOINT(011,L"011"); -TEMPLATE_CA_ENTRYPOINT(012,L"012"); -TEMPLATE_CA_ENTRYPOINT(013,L"013"); -TEMPLATE_CA_ENTRYPOINT(014,L"014"); -TEMPLATE_CA_ENTRYPOINT(015,L"015"); -TEMPLATE_CA_ENTRYPOINT(016,L"016"); -TEMPLATE_CA_ENTRYPOINT(017,L"017"); -TEMPLATE_CA_ENTRYPOINT(018,L"018"); -TEMPLATE_CA_ENTRYPOINT(019,L"019"); -TEMPLATE_CA_ENTRYPOINT(020,L"020"); -TEMPLATE_CA_ENTRYPOINT(021,L"021"); -TEMPLATE_CA_ENTRYPOINT(022,L"022"); -TEMPLATE_CA_ENTRYPOINT(023,L"023"); -TEMPLATE_CA_ENTRYPOINT(024,L"024"); -TEMPLATE_CA_ENTRYPOINT(025,L"025"); -TEMPLATE_CA_ENTRYPOINT(026,L"026"); -TEMPLATE_CA_ENTRYPOINT(027,L"027"); -TEMPLATE_CA_ENTRYPOINT(028,L"028"); -TEMPLATE_CA_ENTRYPOINT(029,L"029"); -TEMPLATE_CA_ENTRYPOINT(030,L"030"); -TEMPLATE_CA_ENTRYPOINT(031,L"031"); -TEMPLATE_CA_ENTRYPOINT(032,L"032"); -TEMPLATE_CA_ENTRYPOINT(033,L"033"); -TEMPLATE_CA_ENTRYPOINT(034,L"034"); -TEMPLATE_CA_ENTRYPOINT(035,L"035"); -TEMPLATE_CA_ENTRYPOINT(036,L"036"); -TEMPLATE_CA_ENTRYPOINT(037,L"037"); -TEMPLATE_CA_ENTRYPOINT(038,L"038"); -TEMPLATE_CA_ENTRYPOINT(039,L"039"); -TEMPLATE_CA_ENTRYPOINT(040,L"040"); -TEMPLATE_CA_ENTRYPOINT(041,L"041"); -TEMPLATE_CA_ENTRYPOINT(042,L"042"); -TEMPLATE_CA_ENTRYPOINT(043,L"043"); -TEMPLATE_CA_ENTRYPOINT(044,L"044"); -TEMPLATE_CA_ENTRYPOINT(045,L"045"); -TEMPLATE_CA_ENTRYPOINT(046,L"046"); -TEMPLATE_CA_ENTRYPOINT(047,L"047"); -TEMPLATE_CA_ENTRYPOINT(048,L"048"); -TEMPLATE_CA_ENTRYPOINT(049,L"049"); -TEMPLATE_CA_ENTRYPOINT(050,L"050"); -TEMPLATE_CA_ENTRYPOINT(051,L"051"); -TEMPLATE_CA_ENTRYPOINT(052,L"052"); -TEMPLATE_CA_ENTRYPOINT(053,L"053"); -TEMPLATE_CA_ENTRYPOINT(054,L"054"); -TEMPLATE_CA_ENTRYPOINT(055,L"055"); -TEMPLATE_CA_ENTRYPOINT(056,L"056"); -TEMPLATE_CA_ENTRYPOINT(057,L"057"); -TEMPLATE_CA_ENTRYPOINT(058,L"058"); -TEMPLATE_CA_ENTRYPOINT(059,L"059"); -TEMPLATE_CA_ENTRYPOINT(060,L"060"); -TEMPLATE_CA_ENTRYPOINT(061,L"061"); -TEMPLATE_CA_ENTRYPOINT(062,L"062"); -TEMPLATE_CA_ENTRYPOINT(063,L"063"); -TEMPLATE_CA_ENTRYPOINT(064,L"064"); -TEMPLATE_CA_ENTRYPOINT(065,L"065"); -TEMPLATE_CA_ENTRYPOINT(066,L"066"); -TEMPLATE_CA_ENTRYPOINT(067,L"067"); -TEMPLATE_CA_ENTRYPOINT(068,L"068"); -TEMPLATE_CA_ENTRYPOINT(069,L"069"); -TEMPLATE_CA_ENTRYPOINT(070,L"070"); -TEMPLATE_CA_ENTRYPOINT(071,L"071"); -TEMPLATE_CA_ENTRYPOINT(072,L"072"); -TEMPLATE_CA_ENTRYPOINT(073,L"073"); -TEMPLATE_CA_ENTRYPOINT(074,L"074"); -TEMPLATE_CA_ENTRYPOINT(075,L"075"); -TEMPLATE_CA_ENTRYPOINT(076,L"076"); -TEMPLATE_CA_ENTRYPOINT(077,L"077"); -TEMPLATE_CA_ENTRYPOINT(078,L"078"); -TEMPLATE_CA_ENTRYPOINT(079,L"079"); -TEMPLATE_CA_ENTRYPOINT(080,L"080"); -TEMPLATE_CA_ENTRYPOINT(081,L"081"); -TEMPLATE_CA_ENTRYPOINT(082,L"082"); -TEMPLATE_CA_ENTRYPOINT(083,L"083"); -TEMPLATE_CA_ENTRYPOINT(084,L"084"); -TEMPLATE_CA_ENTRYPOINT(085,L"085"); -TEMPLATE_CA_ENTRYPOINT(086,L"086"); -TEMPLATE_CA_ENTRYPOINT(087,L"087"); -TEMPLATE_CA_ENTRYPOINT(088,L"088"); -TEMPLATE_CA_ENTRYPOINT(089,L"089"); -TEMPLATE_CA_ENTRYPOINT(090,L"090"); -TEMPLATE_CA_ENTRYPOINT(091,L"091"); -TEMPLATE_CA_ENTRYPOINT(092,L"092"); -TEMPLATE_CA_ENTRYPOINT(093,L"093"); -TEMPLATE_CA_ENTRYPOINT(094,L"094"); -TEMPLATE_CA_ENTRYPOINT(095,L"095"); -TEMPLATE_CA_ENTRYPOINT(096,L"096"); -TEMPLATE_CA_ENTRYPOINT(097,L"097"); -TEMPLATE_CA_ENTRYPOINT(098,L"098"); -TEMPLATE_CA_ENTRYPOINT(099,L"099"); -TEMPLATE_CA_ENTRYPOINT(100,L"100"); -TEMPLATE_CA_ENTRYPOINT(101,L"101"); -TEMPLATE_CA_ENTRYPOINT(102,L"102"); -TEMPLATE_CA_ENTRYPOINT(103,L"103"); -TEMPLATE_CA_ENTRYPOINT(104,L"104"); -TEMPLATE_CA_ENTRYPOINT(105,L"105"); -TEMPLATE_CA_ENTRYPOINT(106,L"106"); -TEMPLATE_CA_ENTRYPOINT(107,L"107"); -TEMPLATE_CA_ENTRYPOINT(108,L"108"); -TEMPLATE_CA_ENTRYPOINT(109,L"109"); -TEMPLATE_CA_ENTRYPOINT(110,L"110"); -TEMPLATE_CA_ENTRYPOINT(111,L"111"); -TEMPLATE_CA_ENTRYPOINT(112,L"112"); -TEMPLATE_CA_ENTRYPOINT(113,L"113"); -TEMPLATE_CA_ENTRYPOINT(114,L"114"); -TEMPLATE_CA_ENTRYPOINT(115,L"115"); -TEMPLATE_CA_ENTRYPOINT(116,L"116"); -TEMPLATE_CA_ENTRYPOINT(117,L"117"); -TEMPLATE_CA_ENTRYPOINT(118,L"118"); -TEMPLATE_CA_ENTRYPOINT(119,L"119"); -TEMPLATE_CA_ENTRYPOINT(120,L"120"); -TEMPLATE_CA_ENTRYPOINT(121,L"121"); -TEMPLATE_CA_ENTRYPOINT(122,L"122"); -TEMPLATE_CA_ENTRYPOINT(123,L"123"); -TEMPLATE_CA_ENTRYPOINT(124,L"124"); -TEMPLATE_CA_ENTRYPOINT(125,L"125"); -TEMPLATE_CA_ENTRYPOINT(126,L"126"); -TEMPLATE_CA_ENTRYPOINT(127,L"127"); - -// Note: Keep in sync with EntryPoints.def diff --git a/src/samples/Dtf/Tools/SfxCA/Extract.cpp b/src/samples/Dtf/Tools/SfxCA/Extract.cpp deleted file mode 100644 index 171cf52f..00000000 --- a/src/samples/Dtf/Tools/SfxCA/Extract.cpp +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" - -//--------------------------------------------------------------------- -// CABINET EXTRACTION -//--------------------------------------------------------------------- - -// Globals make this code unsuited for multhreaded use, -// but FDI doesn't provide any other way to pass context. - -// Handle to the FDI (cab extraction) engine. Need access to this in a callback. -static HFDI g_hfdi; - -// FDI is not unicode-aware, so avoid passing these paths through the callbacks. -static const wchar_t* g_szExtractDir; -static const wchar_t* g_szCabFile; - -// Offset into the source file where the cabinet really starts. -// Used to trick FDI into extracting from a concatenated cabinet. -static int g_lCabOffset; - -// Use the secure CRT version of _wsopen if available. -#ifdef __GOT_SECURE_LIB__ -#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) -#else -#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) -#endif - -/// -/// FDI callback to open a cabinet file. -/// -/// Name of the file to be opened. This parameter -/// is ignored since with our limited use this method is only ever called -/// to open the main cabinet file. -/// Type of operations allowed. -/// Permission setting. -/// Integer file handle, or -1 if the file could not be opened. -/// -/// To support reading from a cabinet that is concatenated onto -/// another file, this function first searches for the offset of the cabinet, -/// then saves that offset for use in recalculating later seeks. -/// -static FNOPEN(CabOpen) -{ - UNREFERENCED_PARAMETER(pszFile); - int hf; - _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); - if (hf != -1) - { - FDICABINETINFO cabInfo; - int length = _lseek(hf, 0, SEEK_END); - for(int offset = 0; offset < length; offset += 256) - { - if (_lseek(hf, offset, SEEK_SET) != offset) break; - if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) - { - g_lCabOffset = offset; - _lseek(hf, offset, SEEK_SET); - return hf; - } - } - _close(hf); - } - return -1; -} - -/// -/// FDI callback to seek within a file. -/// -/// File handle. -/// Seek distance -/// Whether to seek relative to the -/// beginning, current position, or end of the file. -/// Resultant position within the cabinet. -/// -/// To support reading from a cabinet that is concatenated onto -/// another file, this function recalculates seeks based on the -/// offset that was determined when the cabinet was opened. -/// -static FNSEEK(CabSeek) -{ - if (seektype == SEEK_SET) dist += g_lCabOffset; - int pos = _lseek((int) hf, dist, seektype); - pos -= g_lCabOffset; - return pos; -} - -/// -/// Ensures a directory and its parent directory path exists. -/// -/// Directory path, not including file name. -/// 0 if the directory exists or was successfully created, else nonzero. -/// -/// This function modifies characters in szDirPath, but always restores them -/// regardless of error condition. -/// -static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) -{ - int ret = 0; - if (!::CreateDirectoryW(szDirPath, NULL)) - { - UINT err = ::GetLastError(); - if (err != ERROR_ALREADY_EXISTS) - { - // Directory creation failed for some reason other than already existing. - // Try to create the parent directory first. - wchar_t* szLastSlash = NULL; - for (wchar_t* sz = szDirPath; *sz; sz++) - { - if (*sz == L'\\') - { - szLastSlash = sz; - } - } - if (szLastSlash) - { - // Temporarily take one directory off the path and recurse. - *szLastSlash = L'\0'; - ret = EnsureDirectoryExists(szDirPath); - *szLastSlash = L'\\'; - - // Try to create the directory if all parents are created. - if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) - { - err = ::GetLastError(); - if (err != ERROR_ALREADY_EXISTS) - { - ret = -1; - } - } - } - else - { - ret = -1; - } - } - } - return ret; -} - -/// -/// Ensures a file's directory and its parent directory path exists. -/// -/// Path including file name. -/// 0 if the file's directory exists or was successfully created, else nonzero. -/// -/// This function modifies characters in szFilePath, but always restores them -/// regardless of error condition. -/// -static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) -{ - int ret = 0; - wchar_t* szLastSlash = NULL; - for (wchar_t* sz = szFilePath; *sz; sz++) - { - if (*sz == L'\\') - { - szLastSlash = sz; - } - } - if (szLastSlash) - { - *szLastSlash = L'\0'; - ret = EnsureDirectoryExists(szFilePath); - *szLastSlash = L'\\'; - } - return ret; -} - -/// -/// FDI callback for handling files in the cabinet. -/// -/// Type of notification. -/// Structure containing data about the notification. -/// -/// Refer to fdi.h for more comments on this notification callback. -/// -static FNFDINOTIFY(CabNotification) -{ - // fdintCOPY_FILE: - // Called for each file that *starts* in the current cabinet, giving - // the client the opportunity to request that the file be copied or - // skipped. - // Entry: - // pfdin->psz1 = file name in cabinet - // pfdin->cb = uncompressed size of file - // pfdin->date = file date - // pfdin->time = file time - // pfdin->attribs = file attributes - // pfdin->iFolder = file's folder index - // Exit-Success: - // Return non-zero file handle for destination file; FDI writes - // data to this file use the PFNWRITE function supplied to FDICreate, - // and then calls fdintCLOSE_FILE_INFO to close the file and set - // the date, time, and attributes. - // Exit-Failure: - // Returns 0 => Skip file, do not copy - // Returns -1 => Abort FDICopy() call - if (fdint == fdintCOPY_FILE) - { - size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); - size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; - wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); - if (szFilePath == NULL) return -1; - StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); - StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); - MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, - szFilePath + cchFilePath - cchFile, (int) cchFile + 1); - int hf = -1; - if (EnsureFileDirectoryExists(szFilePath) == 0) - { - _wsopen__s(hf, szFilePath, - _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, - _SH_DENYWR, _S_IREAD | _S_IWRITE); - } - return hf; - } - - // fdintCLOSE_FILE_INFO: - // Called after all of the data has been written to a target file. - // This function must close the file and set the file date, time, - // and attributes. - // Entry: - // pfdin->psz1 = file name in cabinet - // pfdin->hf = file handle - // pfdin->date = file date - // pfdin->time = file time - // pfdin->attribs = file attributes - // pfdin->iFolder = file's folder index - // pfdin->cb = Run After Extract (0 - don't run, 1 Run) - // Exit-Success: - // Returns TRUE - // Exit-Failure: - // Returns FALSE, or -1 to abort - else if (fdint == fdintCLOSE_FILE_INFO) - { - _close((int) pfdin->hf); - return TRUE; - } - return 0; -} - -/// -/// Extracts all contents of a cabinet file to a directory. -/// -/// Path to the cabinet file to be extracted. -/// The cabinet may actually start at some offset within the file, -/// as long as that offset is a multiple of 256. -/// Directory where files are to be extracted. -/// This directory must already exist, but should be empty. -/// 0 if the cabinet was extracted successfully, -/// or an error code if any error occurred. -/// -/// The extraction will not overwrite any files in the destination -/// directory; extraction will be interrupted and fail if any files -/// with the same name already exist. -/// -int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) -{ - ERF erf; - // Most of the FDI callbacks can be handled by existing CRT I/O functions. - // For our functionality we only need to handle the open and seek callbacks. - HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, - (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, - CabSeek, cpu80386, &erf); - if (hfdi != NULL) - { - g_hfdi = hfdi; - g_szCabFile = szCabFile; - g_szExtractDir = szExtractDir; - char szEmpty[1] = {0}; - if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) - { - FDIDestroy(hfdi); - return 0; - } - FDIDestroy(hfdi); - } - - return erf.erfOper; -} diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp deleted file mode 100644 index ba59fdf7..00000000 --- a/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp +++ /dev/null @@ -1,629 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" -#include "RemoteMsiSession.h" - - -// -// Ensures that the request buffer is large enough to hold a request, -// reallocating the buffer if necessary. -// It will also reduce the buffer size if the previous allocation was very large. -// -static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired) -{ - // It will also reduce the buffer size if the previous allocation was very large. - if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf)) - { - if (*pszBuf != NULL) - { - SecureZeroMemory(*pszBuf, *pcchBuf); - delete[] *pszBuf; - } - - *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired); - *pszBuf = new wchar_t[*pcchBuf]; - - if (*pszBuf == NULL) - { - return ERROR_OUTOFMEMORY; - } - } - - return ERROR_SUCCESS; -} - -typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); -typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); -typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); -typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); -typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); -typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); -typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); -typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); -typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); -typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); -typedef 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); - -UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - int out1; - UINT ret = (UINT) func(in1, &out1); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - } - return ret; -} - -UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - int in2 = pReq->fields[1].iValue; - int out1; - UINT ret = (UINT) func(in1, in2, &out1); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - } - return ret; -} - -UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - int out1; - UINT ret = (UINT) func(in1, in2, &out1); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - } - return ret; -} - -UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - int in3 = pReq->fields[2].iValue; - int out1; - UINT ret = (UINT) func(in1, in2, in3, &out1); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - } - return ret; -} - -UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - int in3 = pReq->fields[2].iValue; - int in4 = pReq->fields[3].iValue; - int out1; - UINT ret = (UINT) func(in1, in2, in3, in4, &out1); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - } - return ret; -} - -UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - int out1, out2; - UINT ret = (UINT) func(in1, in2, &out1, &out2); - if (ret == 0) - { - pResp->fields[1].vt = VT_I4; - pResp->fields[1].iValue = out1; - pResp->fields[2].vt = VT_I4; - pResp->fields[2].iValue = out2; - } - return ret; -} - -UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) -{ - int in1 = pReq->fields[0].iValue; - szBuf[0] = L'\0'; - DWORD cchValue = cchBuf; - UINT ret = (UINT) func(in1, szBuf, &cchValue); - if (ret == ERROR_MORE_DATA) - { - ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); - if (ret == 0) - { - ret = (UINT) func(in1, szBuf, &cchValue); - } - } - if (ret == 0) - { - pResp->fields[1].vt = VT_LPWSTR; - pResp->fields[1].szValue = szBuf; - } - return ret; -} - -MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) -{ - int in1 = pReq->fields[0].iValue; - szBuf[0] = L'\0'; - DWORD cchValue = cchBuf; - MSIDBERROR ret = func(in1, szBuf, &cchValue); - if (ret == MSIDBERROR_MOREDATA) - { - if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue)) - { - ret = func(in1, szBuf, &cchValue); - } - } - if (ret != MSIDBERROR_MOREDATA) - { - pResp->fields[1].vt = VT_LPWSTR; - pResp->fields[1].szValue = szBuf; - } - return ret; -} - -UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) -{ - int in1 = pReq->fields[0].iValue; - int in2 = pReq->fields[1].iValue; - szBuf[0] = L'\0'; - DWORD cchValue = cchBuf; - UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); - if (ret == ERROR_MORE_DATA) - { - ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); - if (ret == 0) - { - ret = (UINT) func(in1, in2, szBuf, &cchValue); - } - } - if (ret == 0) - { - pResp->fields[1].vt = VT_LPWSTR; - pResp->fields[1].szValue = szBuf; - } - return ret; -} - -UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - szBuf[0] = L'\0'; - DWORD cchValue = cchBuf; - UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); - if (ret == ERROR_MORE_DATA) - { - ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); - if (ret == 0) - { - ret = (UINT) func(in1, in2, szBuf, &cchValue); - } - } - if (ret == 0) - { - pResp->fields[1].vt = VT_LPWSTR; - pResp->fields[1].szValue = szBuf; - } - return ret; -} - -UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) -{ - int in1 = pReq->fields[0].iValue; - wchar_t* in2 = pReq->fields[1].szValue; - int in3 = pReq->fields[2].iValue; - int in4 = pReq->fields[3].iValue; - szBuf[0] = L'\0'; - DWORD cchValue = cchBuf; - int out2, out3; - UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); - if (ret == ERROR_MORE_DATA) - { - ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); - if (ret == 0) - { - ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); - } - } - if (ret == 0) - { - pResp->fields[1].vt = VT_LPWSTR; - pResp->fields[1].szValue = szBuf; - pResp->fields[2].vt = VT_I4; - pResp->fields[2].iValue = out2; - pResp->fields[3].vt = VT_I4; - pResp->fields[3].iValue = out3; - } - return ret; -} - -void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp) -{ - SecureZeroMemory(pResp, sizeof(RequestData)); - - UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024); - - if (0 == ret) - { - switch (id) - { - case RemoteMsiSession::EndSession: - { - this->ExitCode = pReq->fields[0].iValue; - } - break; - case RemoteMsiSession::MsiCloseHandle: - { - MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue; - ret = ::MsiCloseHandle(h); - } - break; - case RemoteMsiSession::MsiProcessMessage: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue; - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; - ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord); - } - break; - case RemoteMsiSession::MsiGetProperty: - { - ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiSetProperty: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szName = pReq->fields[1].szValue; - const wchar_t* szValue = pReq->fields[2].szValue; - ret = ::MsiSetProperty(hInstall, szName, szValue); - } - break; - case RemoteMsiSession::MsiCreateRecord: - { - UINT cParams = pReq->fields[0].uiValue; - ret = ::MsiCreateRecord(cParams); - } - break; - case RemoteMsiSession::MsiRecordGetFieldCount: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - ret = ::MsiRecordGetFieldCount(hRecord); - } - break; - case RemoteMsiSession::MsiRecordGetInteger: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - ret = ::MsiRecordGetInteger(hRecord, iField); - } - break; - case RemoteMsiSession::MsiRecordSetInteger: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - int iValue = pReq->fields[2].iValue; - ret = ::MsiRecordSetInteger(hRecord, iField, iValue); - } - break; - case RemoteMsiSession::MsiRecordGetString: - { - ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiRecordSetString: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - const wchar_t* szValue = pReq->fields[2].szValue; - ret = ::MsiRecordSetString(hRecord, iField, szValue); - } - break; - case RemoteMsiSession::MsiRecordClearData: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - ret = ::MsiRecordClearData(hRecord); - } - break; - case RemoteMsiSession::MsiRecordIsNull: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - ret = ::MsiRecordIsNull(hRecord, iField); - } - break; - case RemoteMsiSession::MsiFormatRecord: - { - ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiGetActiveDatabase: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - ret = (UINT) ::MsiGetActiveDatabase(hInstall); - } - break; - case RemoteMsiSession::MsiDatabaseOpenView: - { - ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp); - } - break; - case RemoteMsiSession::MsiViewExecute: - { - MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue; - ret = ::MsiViewExecute(hView, hRecord); - } - break; - case RemoteMsiSession::MsiViewFetch: - { - ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp); - } - break; - case RemoteMsiSession::MsiViewModify: - { - MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; - MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue; - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; - ret = ::MsiViewModify(hView, eModifyMode, hRecord); - } - break; - case RemoteMsiSession::MsiViewGetError: - { - ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiViewGetColumnInfo: - { - ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp); - } - break; - case RemoteMsiSession::MsiDatabaseGetPrimaryKeys: - { - ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp); - } - break; - case RemoteMsiSession::MsiDatabaseIsTablePersistent: - { - MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szTable = pReq->fields[1].szValue; - ret = ::MsiDatabaseIsTablePersistent(hDb, szTable); - } - break; - case RemoteMsiSession::MsiDoAction: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szAction = pReq->fields[1].szValue; - ret = ::MsiDoAction(hInstall, szAction); - } - break; - case RemoteMsiSession::MsiEnumComponentCosts: - { - ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiEvaluateCondition: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szCondition = pReq->fields[1].szValue; - ret = ::MsiEvaluateCondition(hInstall, szCondition); - } - break; - case RemoteMsiSession::MsiGetComponentState: - { - ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp); - } - break; - case RemoteMsiSession::MsiGetFeatureCost: - { - ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp); - } - break; - case RemoteMsiSession::MsiGetFeatureState: - { - ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp); - } - break; - case RemoteMsiSession::MsiGetFeatureValidStates: - { - ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp); - } - break; - case RemoteMsiSession::MsiGetLanguage: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - ret = ::MsiGetLanguage(hInstall); - } - break; - case RemoteMsiSession::MsiGetLastErrorRecord: - { - ret = ::MsiGetLastErrorRecord(); - } - break; - case RemoteMsiSession::MsiGetMode: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue; - ret = ::MsiGetMode(hInstall, iRunMode); - } - break; - case RemoteMsiSession::MsiGetSourcePath: - { - ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiGetSummaryInformation: - { - ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp); - } - break; - case RemoteMsiSession::MsiGetTargetPath: - { - ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend); - } - break; - case RemoteMsiSession::MsiRecordDataSize: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - ret = ::MsiRecordDataSize(hRecord, iField); - } - break; - case RemoteMsiSession::MsiRecordReadStream: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - DWORD cbRead = (DWORD) pReq->fields[2].uiValue; - ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2); - if (ret == 0) - { - ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead); - if (ret == 0) - { - pResp->fields[1].vt = VT_STREAM; - pResp->fields[1].szValue = m_pBufSend; - pResp->fields[2].vt = VT_I4; - pResp->fields[2].uiValue = (UINT) cbRead; - } - } - } - break; - case RemoteMsiSession::MsiRecordSetStream: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - UINT iField = pReq->fields[1].uiValue; - const wchar_t* szFilePath = pReq->fields[2].szValue; - ret = ::MsiRecordSetStream(hRecord, iField, szFilePath); - } - break; - case RemoteMsiSession::MsiSequence: - { - MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szTable = pReq->fields[1].szValue; - UINT iSequenceMode = pReq->fields[2].uiValue; - ret = ::MsiSequence(hRecord, szTable, iSequenceMode); - } - break; - case RemoteMsiSession::MsiSetComponentState: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szComponent = pReq->fields[1].szValue; - INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; - ret = ::MsiSetComponentState(hInstall, szComponent, iState); - } - break; - case RemoteMsiSession::MsiSetFeatureAttributes: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szFeature = pReq->fields[1].szValue; - DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue; - ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs); - } - break; - case RemoteMsiSession::MsiSetFeatureState: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szFeature = pReq->fields[1].szValue; - INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; - ret = ::MsiSetFeatureState(hInstall, szFeature, iState); - } - break; - case RemoteMsiSession::MsiSetInstallLevel: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - int iInstallLevel = pReq->fields[1].iValue; - ret = ::MsiSetInstallLevel(hInstall, iInstallLevel); - } - break; - case RemoteMsiSession::MsiSetMode: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue; - BOOL fState = (BOOL) pReq->fields[2].iValue; - ret = ::MsiSetMode(hInstall, iRunMode, fState); - } - break; - case RemoteMsiSession::MsiSetTargetPath: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - const wchar_t* szFolder = pReq->fields[1].szValue; - const wchar_t* szFolderPath = pReq->fields[2].szValue; - ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath); - } - break; - case RemoteMsiSession::MsiSummaryInfoGetProperty: - { - MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue; - UINT uiProperty = pReq->fields[1].uiValue; - UINT uiDataType; - int iValue; - FILETIME ftValue; - m_pBufSend[0] = L'\0'; - DWORD cchValue = m_cbBufSend; - ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); - if (ret == ERROR_MORE_DATA) - { - ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue); - if (ret == 0) - { - ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); - } - } - if (ret == 0) - { - pResp->fields[1].vt = VT_UI4; - pResp->fields[1].uiValue = uiDataType; - - switch (uiDataType) - { - case VT_I2: - case VT_I4: - pResp->fields[2].vt = VT_I4; - pResp->fields[2].iValue = iValue; - break; - case VT_FILETIME: - pResp->fields[2].vt = VT_UI4; - pResp->fields[2].iValue = ftValue.dwHighDateTime; - pResp->fields[3].vt = VT_UI4; - pResp->fields[3].iValue = ftValue.dwLowDateTime; - break; - case VT_LPSTR: - pResp->fields[2].vt = VT_LPWSTR; - pResp->fields[2].szValue = m_pBufSend; - break; - } - } - } - break; - case RemoteMsiSession::MsiVerifyDiskSpace: - { - MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; - ret = ::MsiVerifyDiskSpace(hInstall); - } - break; - - default: - { - ret = ERROR_INVALID_FUNCTION; - } - break; - } - } - - pResp->fields[0].vt = VT_UI4; - pResp->fields[0].uiValue = ret; -} diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h deleted file mode 100644 index 90c7c01f..00000000 --- a/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h +++ /dev/null @@ -1,898 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#define LARGE_BUFFER_THRESHOLD 65536 // bytes -#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts - -/////////////////////////////////////////////////////////////////////////////////////// -// RemoteMsiSession // -////////////////////// -// -// Allows accessing MSI APIs from another process using named pipes. -// -class RemoteMsiSession -{ -public: - - // This enumeration MUST stay in sync with the - // managed equivalent in RemotableNativeMethods.cs! - enum RequestId - { - EndSession = 0, - MsiCloseHandle, - MsiCreateRecord, - MsiDatabaseGetPrimaryKeys, - MsiDatabaseIsTablePersistent, - MsiDatabaseOpenView, - MsiDoAction, - MsiEnumComponentCosts, - MsiEvaluateCondition, - MsiFormatRecord, - MsiGetActiveDatabase, - MsiGetComponentState, - MsiGetFeatureCost, - MsiGetFeatureState, - MsiGetFeatureValidStates, - MsiGetLanguage, - MsiGetLastErrorRecord, - MsiGetMode, - MsiGetProperty, - MsiGetSourcePath, - MsiGetSummaryInformation, - MsiGetTargetPath, - MsiProcessMessage, - MsiRecordClearData, - MsiRecordDataSize, - MsiRecordGetFieldCount, - MsiRecordGetInteger, - MsiRecordGetString, - MsiRecordIsNull, - MsiRecordReadStream, - MsiRecordSetInteger, - MsiRecordSetStream, - MsiRecordSetString, - MsiSequence, - MsiSetComponentState, - MsiSetFeatureAttributes, - MsiSetFeatureState, - MsiSetInstallLevel, - MsiSetMode, - MsiSetProperty, - MsiSetTargetPath, - MsiSummaryInfoGetProperty, - MsiVerifyDiskSpace, - MsiViewExecute, - MsiViewFetch, - MsiViewGetError, - MsiViewGetColumnInfo, - MsiViewModify, - }; - - static const int MAX_REQUEST_FIELDS = 4; - - // Used to pass data back and forth for remote API calls, - // including in & out params & return values. - // Only strings and ints are supported. - struct RequestData - { - struct - { - VARENUM vt; - union { - int iValue; - UINT uiValue; - DWORD cchValue; - LPWSTR szValue; - BYTE* sValue; - DWORD cbValue; - }; - } fields[MAX_REQUEST_FIELDS]; - }; - -public: - - // This value is set from the single data parameter in the EndSession request. - // It saves the exit code of the out-of-proc custom action. - int ExitCode; - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession constructor - // - // Creates a new remote session instance, for use either by the server - // or client process. - // - // szName - Identifies the session instance being remoted. The server and - // the client must use the same name. The name should be unique - // enough to avoid conflicting with other instances on the system. - // - // fServer - True if the calling process is the server process, false if the - // calling process is the client process. - // - RemoteMsiSession(const wchar_t* szName, bool fServer=true) - : m_fServer(fServer), - m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"), - m_szPipeName(NULL), - m_hPipe(NULL), - m_fConnecting(false), - m_fConnected(false), - m_hReceiveThread(NULL), - m_hReceiveStopEvent(NULL), - m_pBufReceive(NULL), - m_cbBufReceive(0), - m_pBufSend(NULL), - m_cbBufSend(0), - ExitCode(ERROR_INSTALL_FAILURE) - { - SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED)); - m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession destructor - // - // Closes any open handles and frees any allocated memory. - // - ~RemoteMsiSession() - { - WaitExitCode(); - if (m_hPipe != NULL) - { - CloseHandle(m_hPipe); - m_hPipe = NULL; - } - if (m_overlapped.hEvent != NULL) - { - CloseHandle(m_overlapped.hEvent); - m_overlapped.hEvent = NULL; - } - if (m_szPipeName != NULL) - { - delete[] m_szPipeName; - m_szPipeName = NULL; - } - if (m_pBufReceive != NULL) - { - SecureZeroMemory(m_pBufReceive, m_cbBufReceive); - delete[] m_pBufReceive; - m_pBufReceive = NULL; - } - if (m_pBufSend != NULL) - { - SecureZeroMemory(m_pBufSend, m_cbBufSend); - delete[] m_pBufSend; - m_pBufSend = NULL; - } - m_fConnecting = false; - m_fConnected = false; - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession::WaitExitCode() - // - // Waits for the server processing thread to complete. - // - void WaitExitCode() - { - if (m_hReceiveThread != NULL) - { - SetEvent(m_hReceiveStopEvent); - WaitForSingleObject(m_hReceiveThread, INFINITE); - CloseHandle(m_hReceiveThread); - m_hReceiveThread = NULL; - } - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession::Connect() - // - // Connects the inter-process communication channel. - // (Currently implemented as a named pipe.) - // - // This method must be called first by the server process, then by the client - // process. The method does not block; the server will asynchronously wait - // for the client process to make the connection. - // - // Returns: 0 on success, Win32 error code on failure. - // - virtual DWORD Connect() - { - const wchar_t* szPipePrefix = L"\\\\.\\pipe\\"; - size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1; - m_szPipeName = new wchar_t[cchPipeNameBuf]; - - if (m_szPipeName == NULL) - { - return ERROR_OUTOFMEMORY; - } - else - { - wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix); - wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName); - - if (m_fServer) - { - return this->ConnectPipeServer(); - } - else - { - return this->ConnectPipeClient(); - } - } - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession::IsConnected() - // - // Checks if the server process and client process are currently connected. - // - virtual bool IsConnected() const - { - return m_fConnected; - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession::ProcessRequests() - // - // For use by the service process. Watches for requests in the input buffer and calls - // the callback for each one. - // - // This method does not block; it spawns a separate thread to do the work. - // - // Returns: 0 on success, Win32 error code on failure. - // - virtual DWORD ProcessRequests() - { - return this->StartProcessingReqests(); - } - - ///////////////////////////////////////////////////////////////////////////////////// - // RemoteMsiSession::SendRequest() - // - // For use by the client process. Sends a request to the server and - // synchronously waits on a response, up to the timeout value. - // - // id - ID code of the MSI API call being requested. - // - // pRequest - Pointer to a data structure containing request parameters. - // - // ppResponse - [OUT] Pointer to a location that receives the response parameters. - // - // Returns: 0 on success, Win32 error code on failure. - // Returns WAIT_TIMEOUT if no response was received in time. - // - virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse) - { - if (m_fServer) - { - return ERROR_INVALID_OPERATION; - } - - if (!m_fConnected) - { - *ppResponse = NULL; - return 0; - } - - DWORD dwRet = this->SendRequest(id, pRequest); - if (dwRet != 0) - { - return dwRet; - } - - if (id != EndSession) - { - static RequestData response; - if (ppResponse != NULL) - { - *ppResponse = &response; - } - - return this->ReceiveResponse(id, &response); - } - else - { - CloseHandle(m_hPipe); - m_hPipe = NULL; - m_fConnected = false; - return 0; - } - } - -private: - - // - // Do not allow assignment. - // - RemoteMsiSession& operator=(const RemoteMsiSession&); - - // - // Called only by the server process. - // Create a new thread to handle receiving requests. - // - DWORD StartProcessingReqests() - { - if (!m_fServer || m_hReceiveStopEvent != NULL) - { - return ERROR_INVALID_OPERATION; - } - - DWORD dwRet = 0; - - m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - if (m_hReceiveStopEvent == NULL) - { - dwRet = GetLastError(); - } - else - { - if (m_hReceiveThread != NULL) - { - CloseHandle(m_hReceiveThread); - } - - m_hReceiveThread = CreateThread(NULL, 0, - RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL); - - if (m_hReceiveThread == NULL) - { - dwRet = GetLastError(); - CloseHandle(m_hReceiveStopEvent); - m_hReceiveStopEvent = NULL; - } - } - - return dwRet; - } - - // - // Called only by the watcher process. - // First verify the connection is complete. Then continually read and parse messages, - // invoke the callback, and send the replies. - // - static DWORD WINAPI ProcessRequestsThreadStatic(void* pv) - { - return reinterpret_cast(pv)->ProcessRequestsThread(); - } - - DWORD ProcessRequestsThread() - { - DWORD dwRet; - - dwRet = CompleteConnection(); - if (dwRet != 0) - { - if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0; - } - - while (m_fConnected) - { - RequestId id; - RequestData req; - dwRet = ReceiveRequest(&id, &req); - if (dwRet != 0) - { - if (dwRet == ERROR_OPERATION_ABORTED || - dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA) - { - dwRet = 0; - } - } - else - { - RequestData resp; - ProcessRequest(id, &req, &resp); - - if (id == EndSession) - { - break; - } - - dwRet = SendResponse(id, &resp); - if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA) - { - dwRet = 0; - } - } - } - - CloseHandle(m_hReceiveStopEvent); - m_hReceiveStopEvent = NULL; - return dwRet; - } - - // - // Called only by the server process's receive thread. - // Read one request into a RequestData object. - // - DWORD ReceiveRequest(RequestId* pId, RequestData* pReq) - { - DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId)); - - if (dwRet == 0) - { - dwRet = this->ReadRequestData(pReq); - } - - return dwRet; - } - - // - // Called by the server process's receive thread or the client's request call - // to read the response. Read data from the pipe, allowing interruption by the - // stop event if on the server. - // - DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead) - { - DWORD dwRet = 0; - DWORD dwTotalBytesRead = 0; - - while (dwRet == 0 && dwTotalBytesRead < cbRead) - { - DWORD dwBytesReadThisTime; - ResetEvent(m_overlapped.hEvent); - if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped)) - { - dwRet = GetLastError(); - if (dwRet == ERROR_IO_PENDING) - { - if (m_fServer) - { - HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; - dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); - } - else - { - dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); - } - - if (dwRet == WAIT_OBJECT_0) - { - if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE)) - { - dwRet = GetLastError(); - } - } - else if (dwRet == WAIT_FAILED) - { - dwRet = GetLastError(); - } - else - { - dwRet = ERROR_OPERATION_ABORTED; - } - } - } - - dwTotalBytesRead += dwBytesReadThisTime; - } - - if (dwRet != 0) - { - if (m_fServer) - { - CancelIo(m_hPipe); - DisconnectNamedPipe(m_hPipe); - } - else - { - CloseHandle(m_hPipe); - m_hPipe = NULL; - } - m_fConnected = false; - } - - return dwRet; - } - - // - // Called only by the server process. - // Given a request, invoke the MSI API and return the response. - // This is implemented in RemoteMsi.cpp. - // - void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp); - - // - // Called only by the client process. - // Send request data over the pipe. - // - DWORD SendRequest(RequestId id, const RequestData* pRequest) - { - DWORD dwRet = WriteRequestData(id, pRequest); - - if (dwRet != 0) - { - m_fConnected = false; - CloseHandle(m_hPipe); - m_hPipe = NULL; - } - - return dwRet; - } - - // - // Called only by the server process. - // Just send a response over the pipe. - // - DWORD SendResponse(RequestId id, const RequestData* pResp) - { - DWORD dwRet = WriteRequestData(id, pResp); - - if (dwRet != 0) - { - DisconnectNamedPipe(m_hPipe); - m_fConnected = false; - } - - return dwRet; - } - - // - // Called either by the client or server process. - // Writes data to the pipe for a request or response. - // - DWORD WriteRequestData(RequestId id, const RequestData* pReq) - { - DWORD dwRet = 0; - - RequestData req = *pReq; // Make a copy because the const data can't be changed. - - dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId)); - if (dwRet != 0) - { - return dwRet; - } - - BYTE* sValues[MAX_REQUEST_FIELDS] = {0}; - for (int i = 0; i < MAX_REQUEST_FIELDS; i++) - { - if (req.fields[i].vt == VT_LPWSTR) - { - sValues[i] = (BYTE*) req.fields[i].szValue; - req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue); - } - else if (req.fields[i].vt == VT_STREAM) - { - sValues[i] = req.fields[i].sValue; - req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue; - } - } - - dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData)); - if (dwRet != 0) - { - return dwRet; - } - - for (int i = 0; i < MAX_REQUEST_FIELDS; i++) - { - if (sValues[i] != NULL) - { - DWORD cbValue; - if (req.fields[i].vt == VT_LPWSTR) - { - cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR); - } - else - { - cbValue = req.fields[i].cbValue; - } - - dwRet = this->WritePipe(const_cast (sValues[i]), cbValue); - if (dwRet != 0) - { - break; - } - } - } - - return dwRet; - } - - // - // Called when writing a request or response. Writes data to - // the pipe, allowing interruption by the stop event if on the server. - // - DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite) - { - DWORD dwRet = 0; - DWORD dwTotalBytesWritten = 0; - - while (dwRet == 0 && dwTotalBytesWritten < cbWrite) - { - DWORD dwBytesWrittenThisTime; - ResetEvent(m_overlapped.hEvent); - if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped)) - { - dwRet = GetLastError(); - if (dwRet == ERROR_IO_PENDING) - { - if (m_fServer) - { - HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; - dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); - } - else - { - dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); - } - - if (dwRet == WAIT_OBJECT_0) - { - if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE)) - { - dwRet = GetLastError(); - } - } - else if (dwRet == WAIT_FAILED) - { - dwRet = GetLastError(); - } - else - { - dwRet = ERROR_OPERATION_ABORTED; - } - } - } - - dwTotalBytesWritten += dwBytesWrittenThisTime; - } - - return dwRet; - } - - // - // Called either by the client or server process. - // Reads data from the pipe for a request or response. - // - DWORD ReadRequestData(RequestData* pReq) - { - DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData)); - - if (dwRet == 0) - { - DWORD cbData = 0; - for (int i = 0; i < MAX_REQUEST_FIELDS; i++) - { - if (pReq->fields[i].vt == VT_LPWSTR) - { - cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); - } - else if (pReq->fields[i].vt == VT_STREAM) - { - cbData += pReq->fields[i].cbValue; - } - } - - if (cbData > 0) - { - if (!CheckRequestDataBuf(cbData)) - { - return ERROR_OUTOFMEMORY; - } - - dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData); - if (dwRet == 0) - { - DWORD dwOffset = 0; - for (int i = 0; i < MAX_REQUEST_FIELDS; i++) - { - if (pReq->fields[i].vt == VT_LPWSTR) - { - LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset); - dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); - pReq->fields[i].szValue = szTemp; - } - else if (pReq->fields[i].vt == VT_STREAM) - { - BYTE* sTemp = m_pBufReceive + dwOffset; - dwOffset += pReq->fields[i].cbValue; - pReq->fields[i].sValue = sTemp; - } - } - } - } - } - - return dwRet; - } - - // - // Called only by the client process. - // Wait for a response on the pipe. If no response is received before the timeout, - // then give up and close the connection. - // - DWORD ReceiveResponse(RequestId id, RequestData* pResp) - { - RequestId responseId; - DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId)); - if (dwRet == 0 && responseId != id) - { - dwRet = ERROR_OPERATION_ABORTED; - } - - if (dwRet == 0) - { - dwRet = this->ReadRequestData(pResp); - } - - return dwRet; - } - - // - // Called only by the server process's receive thread. - // Try to complete and verify an asynchronous connection operation. - // - DWORD CompleteConnection() - { - DWORD dwRet = 0; - if (m_fConnecting) - { - HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; - DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); - - if (dwWaitRes == WAIT_OBJECT_0) - { - m_fConnecting = false; - - DWORD dwUnused; - if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE)) - { - m_fConnected = true; - } - else - { - dwRet = GetLastError(); - } - } - else if (dwWaitRes == WAIT_FAILED) - { - CancelIo(m_hPipe); - dwRet = GetLastError(); - } - else - { - CancelIo(m_hPipe); - dwRet = ERROR_OPERATION_ABORTED; - } - } - return dwRet; - } - - // - // Called only by the server process. - // Creates a named pipe instance and begins asynchronously waiting - // for a connection from the client process. - // - DWORD ConnectPipeServer() - { - DWORD dwRet = 0; - const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes - m_hPipe = CreateNamedPipe( - m_szPipeName, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, - 1, BUFSIZE, BUFSIZE, 0, NULL); - if (m_hPipe == INVALID_HANDLE_VALUE) - { - m_hPipe = NULL; - dwRet = GetLastError(); - } - else if (ConnectNamedPipe(m_hPipe, &m_overlapped)) - { - m_fConnected = true; - } - else - { - dwRet = GetLastError(); - - if (dwRet == ERROR_PIPE_BUSY) - { - // All pipe instances are busy, so wait for a maximum of 20 seconds - dwRet = 0; - if (WaitNamedPipe(m_szPipeName, 20000)) - { - m_fConnected = true; - } - else - { - dwRet = GetLastError(); - } - } - - if (dwRet == ERROR_IO_PENDING) - { - dwRet = 0; - m_fConnecting = true; - } - } - return dwRet; - } - - // - // Called only by the client process. - // Attemps to open a connection to an existing named pipe instance - // which should have already been created by the server process. - // - DWORD ConnectPipeClient() - { - DWORD dwRet = 0; - m_hPipe = CreateFile( - m_szPipeName, GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - if (m_hPipe != INVALID_HANDLE_VALUE) - { - m_fConnected = true; - } - else - { - m_hPipe = NULL; - dwRet = GetLastError(); - } - return dwRet; - } - - // - // Ensures that the request buffer is large enough to hold a request, - // reallocating the buffer if necessary. - // It will also reduce the buffer size if the previous allocation was very large. - // - BOOL CheckRequestDataBuf(DWORD cbBuf) - { - if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive)) - { - if (m_pBufReceive != NULL) - { - SecureZeroMemory(m_pBufReceive, m_cbBufReceive); - delete[] m_pBufReceive; - } - m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf); - m_pBufReceive = new BYTE[m_cbBufReceive]; - if (m_pBufReceive == NULL) - { - m_cbBufReceive = 0; - } - } - return m_pBufReceive != NULL; - } - -private: - - // Name of this instance. - const wchar_t* m_szName; - - // "\\.\pipe\name" - wchar_t* m_szPipeName; - - // Handle to the pipe instance. - HANDLE m_hPipe; - - // Handle to the thread that receives requests. - HANDLE m_hReceiveThread; - - // Handle to the event used to signal the receive thread to exit. - HANDLE m_hReceiveStopEvent; - - // All pipe I/O is done in overlapped mode to avoid unintentional blocking. - OVERLAPPED m_overlapped; - - // Dynamically-resized buffer for receiving requests. - BYTE* m_pBufReceive; - - // Current size of the receive request buffer. - DWORD m_cbBufReceive; - - // Dynamically-resized buffer for sending requests. - wchar_t* m_pBufSend; - - // Current size of the send request buffer. - DWORD m_cbBufSend; - - // True if this is the server process, false if this is the client process. - const bool m_fServer; - - // True if an asynchronous connection operation is currently in progress. - bool m_fConnecting; - - // True if the pipe is currently connected. - bool m_fConnected; -}; diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp deleted file mode 100644 index 06319f1e..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" -#include "EntryPoints.h" -#include "SfxUtil.h" - -#define MANAGED_CAs_OUT_OF_PROC 1 - -HMODULE g_hModule; -bool g_fRunningOutOfProc = false; - -RemoteMsiSession* g_pRemote = NULL; - -// Prototypes for local functions. -// See the function definitions for comments. - -bool InvokeManagedCustomAction(MSIHANDLE hSession, - _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); - -/// -/// Entry-point for the CA DLL when re-launched as a separate process; -/// connects the comm channel for remote MSI APIs, then invokes the -/// managed custom action entry-point. -/// -/// -/// Do not change the parameters or calling-convention: RUNDLL32 -/// requires this exact signature. -/// -extern "C" -void __stdcall InvokeManagedCustomActionOutOfProc( - __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) -{ - UNREFERENCED_PARAMETER(hwnd); - UNREFERENCED_PARAMETER(hinst); - UNREFERENCED_PARAMETER(nCmdShow); - - g_fRunningOutOfProc = true; - - const wchar_t* szSessionName = szCmdLine; - MSIHANDLE hSession; - const wchar_t* szEntryPoint; - - int i; - for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); - if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; - hSession = _wtoi(szCmdLine + i); - - for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); - if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; - szEntryPoint = szCmdLine + i; - - g_pRemote = new RemoteMsiSession(szSessionName, false); - g_pRemote->Connect(); - - int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); - - RemoteMsiSession::RequestData requestData; - SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); - requestData.fields[0].vt = VT_I4; - requestData.fields[0].iValue = ret; - g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); - delete g_pRemote; -} - -/// -/// Re-launch this CA DLL as a separate process, and setup a comm channel -/// for remote MSI API calls back to this process. -/// -int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) -{ - wchar_t szSessionName[100] = {0}; - swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); - - RemoteMsiSession remote(szSessionName, true); - - DWORD ret = remote.Connect(); - if (ret != 0) - { - Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); - return ERROR_INSTALL_FAILURE; - } - - ret = remote.ProcessRequests(); - if (ret != 0) - { - Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); - return ERROR_INSTALL_FAILURE; - } - - wchar_t szModule[MAX_PATH] = {0}; - GetModuleFileName(g_hModule, szModule, MAX_PATH); - - const wchar_t* rundll32 = L"rundll32.exe"; - wchar_t szRunDll32Path[MAX_PATH] = {0}; - GetSystemDirectory(szRunDll32Path, MAX_PATH); - wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); - wcscat_s(szRunDll32Path, MAX_PATH, rundll32); - - const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; - wchar_t szCommandLine[1024] = {0}; - swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", - rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); - - STARTUPINFO si; - SecureZeroMemory(&si, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - - PROCESS_INFORMATION pi; - SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); - - if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, - 0, NULL, NULL, &si, &pi)) - { - DWORD err = GetLastError(); - Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); - return ERROR_INSTALL_FAILURE; - } - - DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); - if (dwWait != WAIT_OBJECT_0) - { - DWORD err = GetLastError(); - Log(hSession, L"Failed to wait for CA process. Error code: %d", err); - return ERROR_INSTALL_FAILURE; - } - - DWORD dwExitCode; - BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); - if (!bRet) - { - DWORD err = GetLastError(); - Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); - return ERROR_INSTALL_FAILURE; - } - else if (dwExitCode != 0) - { - Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); - return ERROR_INSTALL_FAILURE; - } - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - - remote.WaitExitCode(); - return remote.ExitCode; -} - -/// -/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to -/// call MSI APIs remotely. -/// -void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) -{ - if (g_fRunningOutOfProc) - { - g_pRemote->SendRequest(id, pRequest, ppResponse); - } - else - { - *ppResponse = NULL; - } -} - -/// -/// Invokes a managed custom action from native code by -/// extracting the package to a temporary working directory -/// then hosting the CLR and locating and calling the entrypoint. -/// -/// Handle to the installation session. -/// Passed to custom action entrypoints by the installer engine. -/// Directory containing the CA binaries -/// and the CustomAction.config file defining the entrypoints. -/// This may be NULL, in which case the current module must have -/// a concatenated cabinet containing those files, which will be -/// extracted to a temporary directory. -/// Name of the CA entrypoint to be invoked. -/// This must be either an explicit "AssemblyName!Namespace.Class.Method" -/// string, or a simple name that maps to a full entrypoint definition -/// in CustomAction.config. -/// The value returned by the managed custom action method, -/// or ERROR_INSTALL_FAILURE if the CA could not be invoked. -int InvokeCustomAction(MSIHANDLE hSession, - const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) -{ -#ifdef MANAGED_CAs_OUT_OF_PROC - if (!g_fRunningOutOfProc && szWorkingDir == NULL) - { - return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); - } -#endif - - wchar_t szTempDir[MAX_PATH]; - bool fDeleteTemp = false; - if (szWorkingDir == NULL) - { - if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) - { - return ERROR_INSTALL_FAILURE; - } - szWorkingDir = szTempDir; - fDeleteTemp = true; - } - - wchar_t szConfigFilePath[MAX_PATH + 20]; - StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); - StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); - - const wchar_t* szConfigFile = szConfigFilePath; - if (!::PathFileExists(szConfigFilePath)) - { - szConfigFile = NULL; - } - - wchar_t szWIAssembly[MAX_PATH + 50]; - StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); - StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); - - int iResult = ERROR_INSTALL_FAILURE; - ICorRuntimeHost* pHost; - if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) - { - _AppDomain* pAppDomain; - if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, - szConfigFile, &pAppDomain)) - { - if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) - { - iResult = ERROR_INSTALL_FAILURE; - } - HRESULT hr = pHost->UnloadDomain(pAppDomain); - if (FAILED(hr)) - { - Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); - } - pAppDomain->Release(); - } - - pHost->Stop(); - pHost->Release(); - } - - if (fDeleteTemp) - { - DeleteDirectory(szTempDir); - } - return iResult; -} - -/// -/// Called by the system when the DLL is loaded. -/// Saves the module handle for later use. -/// -BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) -{ - UNREFERENCED_PARAMETER(pReserved); - - switch (dwReason) - { - case DLL_PROCESS_ATTACH: - g_hModule = hModule; - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} - -/// -/// Loads and invokes the managed portion of the proxy. -/// -/// Handle to the installer session, -/// used for logging errors and to be passed on to the custom action. -/// AppDomain which has its application -/// base set to the CA working directory. -/// Name of the CA entrypoint to be invoked. -/// This must be either an explicit "AssemblyName!Namespace.Class.Method" -/// string, or a simple name that maps to a full entrypoint definition -/// in CustomAction.config. -/// Return value of the invoked custom -/// action method. -/// True if the managed proxy was invoked successfully, -/// false if there was some error. Note the custom action itself may -/// return an error via piResult while this method still returns true -/// since the invocation was successful. -bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, - const wchar_t* szEntryPoint, int* piResult) -{ - VARIANT vResult; - ::VariantInit(&vResult); - - const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); - const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; - const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; - const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); - - _MethodInfo* pCAInvokeMethod; - if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, - szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) - { - return false; - } - - HRESULT hr; - VARIANT vNull; - vNull.vt = VT_EMPTY; - SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); - VARIANT vSessionHandle; - vSessionHandle.vt = VT_I4; - vSessionHandle.intVal = hSession; - LONG index = 0; - hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); - if (FAILED(hr)) goto LExit; - VARIANT vEntryPoint; - vEntryPoint.vt = VT_BSTR; - vEntryPoint.bstrVal = SysAllocString(szEntryPoint); - if (vEntryPoint.bstrVal == NULL) - { - hr = E_OUTOFMEMORY; - goto LExit; - } - index = 1; - hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); - if (FAILED(hr)) goto LExit; - VARIANT vRemotingFunctionPtr; -#pragma warning(push) -#pragma warning(disable:4127) // conditional expression is constant - if (f64bit) -#pragma warning(pop) - { - vRemotingFunctionPtr.vt = VT_I8; - vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); - } - else - { - vRemotingFunctionPtr.vt = VT_I4; -#pragma warning(push) -#pragma warning(disable:4302) // truncation -#pragma warning(disable:4311) // pointer truncation - vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); -#pragma warning(pop) - } - index = 2; - hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); - if (FAILED(hr)) goto LExit; - - hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); - -LExit: - SafeArrayDestroy(saArgs); - pCAInvokeMethod->Release(); - - if (FAILED(hr)) - { - Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); - return false; - } - - *piResult = vResult.intVal; - return true; -} - diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.rc b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc deleted file mode 100644 index 4d78194b..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxCA.rc +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#define VER_DLL -#define VER_LANG_NEUTRAL -#define VER_ORIGINAL_FILENAME "SfxCA.dll" -#define VER_INTERNAL_NAME "SfxCA" -#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action" - -// Additional resources here - diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj deleted file mode 100644 index 4684d6e0..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - {55D5BA28-D427-4F53-80C2-FE9EF23C1553} - DynamicLibrary - SfxCA - Unicode - EntryPoints.def - - - - - msi.lib;cabinet.lib;shlwapi.lib - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters deleted file mode 100644 index a5ebf693..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - {81c92f68-18c2-4cd4-a588-5c3616860dd9} - - - {6cdc30ee-e14d-4679-b92e-3e080535e53b} - - - {1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d} - - - - - Resource Files - - - - - Resource Files - - - - \ No newline at end of file diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp deleted file mode 100644 index 1bf2c5b2..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" -#include "SfxUtil.h" - -/// -/// Writes a formatted message to the MSI log. -/// Does out-of-proc MSI calls if necessary. -/// -void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) -{ - const int LOG_BUFSIZE = 4096; - wchar_t szBuf[LOG_BUFSIZE]; - va_list args; - va_start(args, szMessage); - StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); - - if (!g_fRunningOutOfProc || NULL == g_pRemote) - { - MSIHANDLE hRec = MsiCreateRecord(1); - MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); - MsiRecordSetString(hRec, 1, szBuf); - MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); - MsiCloseHandle(hRec); - } - else - { - // Logging is the only remote-MSI operation done from unmanaged code. - // It's not very convenient here because part of the infrastructure - // for remote MSI APIs is on the managed side. - - RemoteMsiSession::RequestData req; - RemoteMsiSession::RequestData* pResp = NULL; - SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); - - req.fields[0].vt = VT_UI4; - req.fields[0].uiValue = 1; - g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); - MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; - - req.fields[0].vt = VT_I4; - req.fields[0].iValue = (int) hRec; - req.fields[1].vt = VT_UI4; - req.fields[1].uiValue = 0; - req.fields[2].vt = VT_LPWSTR; - req.fields[2].szValue = L"SFXCA: [1]"; - g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); - - req.fields[0].vt = VT_I4; - req.fields[0].iValue = (int) hRec; - req.fields[1].vt = VT_UI4; - req.fields[1].uiValue = 1; - req.fields[2].vt = VT_LPWSTR; - req.fields[2].szValue = szBuf; - g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); - - req.fields[0].vt = VT_I4; - req.fields[0].iValue = (int) hSession; - req.fields[1].vt = VT_I4; - req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; - req.fields[2].vt = VT_I4; - req.fields[2].iValue = (int) hRec; - g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); - - req.fields[0].vt = VT_I4; - req.fields[0].iValue = (int) hRec; - req.fields[1].vt = VT_EMPTY; - req.fields[2].vt = VT_EMPTY; - g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); - } -} - -/// -/// Deletes a directory, including all files and subdirectories. -/// -/// Path to the directory to delete, -/// not including a trailing backslash. -/// True if the directory was successfully deleted, or false -/// if the deletion failed (most likely because some files were locked). -/// -bool DeleteDirectory(const wchar_t* szDir) -{ - size_t cchDir = wcslen(szDir); - size_t cchPathBuf = cchDir + 3 + MAX_PATH; - wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); - if (szPath == NULL) return false; - StringCchCopy(szPath, cchPathBuf, szDir); - StringCchCat(szPath, cchPathBuf, L"\\*"); - WIN32_FIND_DATA fd; - HANDLE hSearch = FindFirstFile(szPath, &fd); - while (hSearch != INVALID_HANDLE_VALUE) - { - StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); - if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) - { - DeleteDirectory(szPath); - } - } - else - { - DeleteFile(szPath); - } - if (!FindNextFile(hSearch, &fd)) - { - FindClose(hSearch); - hSearch = INVALID_HANDLE_VALUE; - } - } - return RemoveDirectory(szDir) != 0; -} - -bool DirectoryExists(const wchar_t* szDir) -{ - if (szDir != NULL) - { - DWORD dwAttrs = GetFileAttributes(szDir); - if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) - { - return true; - } - } - return false; -} - -/// -/// Extracts a cabinet that is concatenated to a module -/// to a new temporary directory. -/// -/// Handle to the installer session, -/// used just for logging. -/// Module that has the concatenated cabinet. -/// Buffer for returning the path of the -/// created temp directory. -/// Size in characters of the buffer. -/// True if the files were extracted, or false if the -/// buffer was too small or the directory could not be created -/// or the extraction failed for some other reason. -__success(return != false) -bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, - __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) -{ - wchar_t szModule[MAX_PATH]; - DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); - if (cchCopied == 0) - { - Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); - return false; - } - else if (cchCopied == MAX_PATH - 1) - { - Log(hSession, L"Failed to get module path -- path is too long."); - return false; - } - - if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) - { - Log(hSession, L"Temp directory buffer is NULL or too small."); - return false; - } - StringCchCopy(szTempDir, cchTempDirBuf, szModule); - StringCchCat(szTempDir, cchTempDirBuf, L"-"); - - DWORD cchTempDir = (DWORD) wcslen(szTempDir); - for (int i = 0; DirectoryExists(szTempDir); i++) - { - swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); - } - - if (!CreateDirectory(szTempDir, NULL)) - { - cchCopied = GetTempPath(cchTempDirBuf, szTempDir); - if (cchCopied == 0 || cchCopied >= cchTempDirBuf) - { - Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError()); - return false; - } - - wchar_t* szModuleName = wcsrchr(szModule, L'\\'); - if (szModuleName == NULL) szModuleName = szModule; - else szModuleName = szModuleName + 1; - StringCchCat(szTempDir, cchTempDirBuf, szModuleName); - StringCchCat(szTempDir, cchTempDirBuf, L"-"); - - cchTempDir = (DWORD) wcslen(szTempDir); - for (int i = 0; DirectoryExists(szTempDir); i++) - { - swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); - } - - if (!CreateDirectory(szTempDir, NULL)) - { - Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError()); - return false; - } - } - - Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); - int err = ExtractCabinet(szModule, szTempDir); - if (err != 0) - { - Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); - DeleteDirectory(szTempDir); - return false; - } - return true; -} - diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.h b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h deleted file mode 100644 index af12d8dd..00000000 --- a/src/samples/Dtf/Tools/SfxCA/SfxUtil.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "RemoteMsiSession.h" - -void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); - -int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); - -bool DeleteDirectory(const wchar_t* szDir); - -__success(return != false) -bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, - __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); - -bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, - const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); - -bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, - const wchar_t* szName, const wchar_t* szAppBase, - const wchar_t* szConfigFile, _AppDomain** ppAppDomain); - -bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, - const wchar_t* szAssembly, const wchar_t* szClass, - const wchar_t* szMethod, _MethodInfo** ppCAMethod); - -extern HMODULE g_hModule; -extern bool g_fRunningOutOfProc; - -extern RemoteMsiSession* g_pRemote; - - diff --git a/src/samples/Dtf/Tools/SfxCA/packages.config b/src/samples/Dtf/Tools/SfxCA/packages.config deleted file mode 100644 index 1ffaa8df..00000000 --- a/src/samples/Dtf/Tools/SfxCA/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.cpp b/src/samples/Dtf/Tools/SfxCA/precomp.cpp deleted file mode 100644 index ce82c1d7..00000000 --- a/src/samples/Dtf/Tools/SfxCA/precomp.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -#include "precomp.h" \ No newline at end of file diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.h b/src/samples/Dtf/Tools/SfxCA/precomp.h deleted file mode 100644 index 48d4f011..00000000 --- a/src/samples/Dtf/Tools/SfxCA/precomp.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#import raw_interfaces_only rename("ReportEvent", "CorReportEvent") -using namespace mscorlib; diff --git a/src/samples/Dtf/Tools/Tools.proj b/src/samples/Dtf/Tools/Tools.proj deleted file mode 100644 index 751247dc..00000000 --- a/src/samples/Dtf/Tools/Tools.proj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - Platform=x64 - - - - - diff --git a/src/test/dtf/Directory.Build.props b/src/test/dtf/Directory.Build.props new file mode 100644 index 00000000..0035a9e6 --- /dev/null +++ b/src/test/dtf/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + IntegrationDtf + false + + + + + diff --git a/src/test/dtf/Directory.Build.targets b/src/test/dtf/Directory.Build.targets new file mode 100644 index 00000000..4e97b6ca --- /dev/null +++ b/src/test/dtf/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/dtf/DtfE2ETests.sln b/src/test/dtf/DtfE2ETests.sln new file mode 100644 index 00000000..39d8cf08 --- /dev/null +++ b/src/test/dtf/DtfE2ETests.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedUI", "EmbeddedUI\EmbeddedUI.csproj", "{864B8C50-7895-4485-AC89-900D86FD8C0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleCA", "SampleCA\SampleCA.csproj", "{8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.Build.0 = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F53B9CC-6FBE-493D-9C9A-09B2AD578CE7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/test/dtf/EmbeddedUI/AssemblyInfo.cs b/src/test/dtf/EmbeddedUI/AssemblyInfo.cs new file mode 100644 index 00000000..27aeb535 --- /dev/null +++ b/src/test/dtf/EmbeddedUI/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Reflection; + +[assembly: AssemblyDescription("Sample managed embedded external UI")] diff --git a/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj b/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj new file mode 100644 index 00000000..9f745a19 --- /dev/null +++ b/src/test/dtf/EmbeddedUI/EmbeddedUI.csproj @@ -0,0 +1,49 @@ + + + {864B8C50-7895-4485-AC89-900D86FD8C0D} + Library + WixToolset.Samples.EmbeddedUI + WixToolset.Samples.EmbeddedUI + v3.5 + 512 + + + + + + + + SetupWizard.xaml + + + + + + MSBuild:Compile + Designer + + + + + + 3.0 + + + 3.0 + + + + 3.5 + + + + 3.0 + + + + + + + + + diff --git a/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs b/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs new file mode 100644 index 00000000..3d75081c --- /dev/null +++ b/src/test/dtf/EmbeddedUI/InstallProgressCounter.cs @@ -0,0 +1,174 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using WixToolset.Dtf.WindowsInstaller; + + /// + /// Tracks MSI progress messages and converts them to usable progress. + /// + public class InstallProgressCounter + { + private int total; + private int completed; + private int step; + private bool moveForward; + private bool enableActionData; + private int progressPhase; + private double scriptPhaseWeight; + + public InstallProgressCounter() : this(0.3) + { + } + + public InstallProgressCounter(double scriptPhaseWeight) + { + if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1)) + { + throw new ArgumentOutOfRangeException("scriptPhaseWeight"); + } + + this.scriptPhaseWeight = scriptPhaseWeight; + } + + /// + /// Gets a number between 0 and 1 that indicates the overall installation progress. + /// + public double Progress { get; private set; } + + public void ProcessMessage(InstallMessage messageType, Record messageRecord) + { + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. + + switch (messageType) + { + case InstallMessage.ActionStart: + if (this.enableActionData) + { + this.enableActionData = false; + } + break; + + case InstallMessage.ActionData: + if (this.enableActionData) + { + if (this.moveForward) + { + this.completed += this.step; + } + else + { + this.completed -= this.step; + } + + this.UpdateProgress(); + } + break; + + case InstallMessage.Progress: + this.ProcessProgressMessage(messageRecord); + break; + } + } + + private void ProcessProgressMessage(Record progressRecord) + { + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. + + if (progressRecord == null || progressRecord.FieldCount == 0) + { + return; + } + + int fieldCount = progressRecord.FieldCount; + int progressType = progressRecord.GetInteger(1); + string progressTypeString = String.Empty; + switch (progressType) + { + case 0: // Master progress reset + if (fieldCount < 4) + { + return; + } + + this.progressPhase++; + + this.total = progressRecord.GetInteger(2); + if (this.progressPhase == 1) + { + // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase + // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress + // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this + // "close" and deal with the rest. + this.total += 50; + } + + this.moveForward = (progressRecord.GetInteger(3) == 0); + this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max + this.enableActionData = false; + + this.UpdateProgress(); + break; + + case 1: // Action info + if (fieldCount < 3) + { + return; + } + + if (progressRecord.GetInteger(3) == 0) + { + this.enableActionData = false; + } + else + { + this.enableActionData = true; + this.step = progressRecord.GetInteger(2); + } + break; + + case 2: // Progress report + if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0) + { + return; + } + + if (this.moveForward) + { + this.completed += progressRecord.GetInteger(2); + } + else + { + this.completed -= progressRecord.GetInteger(2); + } + + this.UpdateProgress(); + break; + + case 3: // Progress total addition + this.total += progressRecord.GetInteger(2); + break; + } + } + + private void UpdateProgress() + { + if (this.progressPhase < 1 || this.total == 0) + { + this.Progress = 0; + } + else if (this.progressPhase == 1) + { + this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total; + } + else if (this.progressPhase == 2) + { + this.Progress = this.scriptPhaseWeight + + (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total; + } + else + { + this.Progress = 1; + } + } + } +} diff --git a/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs new file mode 100644 index 00000000..b9cd213a --- /dev/null +++ b/src/test/dtf/EmbeddedUI/SampleEmbeddedUI.cs @@ -0,0 +1,130 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Threading; + using System.Windows; + using System.Windows.Threading; + using WixToolset.Dtf.WindowsInstaller; + using Application = System.Windows.Application; + + public class SampleEmbeddedUI : IEmbeddedUI + { + private Thread appThread; + private Application app; + private SetupWizard setupWizard; + private ManualResetEvent installStartEvent; + private ManualResetEvent installExitEvent; + + /// + /// Initializes the embedded UI. + /// + /// Handle to the installer which can be used to get and set properties. + /// The handle is only valid for the duration of this method call. + /// Path to the directory that contains all the files from the MsiEmbeddedUI table. + /// On entry, contains the current UI level for the installation. After this + /// method returns, the installer resets the UI level to the returned value of this parameter. + /// True if the embedded UI was successfully initialized; false if the installation + /// should continue without the embedded UI. + /// The installation was canceled by the user. + /// The embedded UI failed to initialize and + /// causes the installation to fail. + public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel) + { + if (session != null) + { + if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full) + { + // Don't show custom UI when the UI level is set to basic. + return false; + + // An embedded UI could display an alternate dialog sequence for reduced or + // basic modes, but it's not implemented here. We'll just fall back to the + // built-in MSI basic UI. + } + + if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase)) + { + // Don't show custom UI when uninstalling. + return false; + + // An embedded UI could display an uninstall wizard, it's just not imlemented here. + } + } + + // Start the setup wizard on a separate thread. + this.installStartEvent = new ManualResetEvent(false); + this.installExitEvent = new ManualResetEvent(false); + this.appThread = new Thread(this.Run); + this.appThread.SetApartmentState(ApartmentState.STA); + this.appThread.Start(); + + // Wait for the setup wizard to either kickoff the install or prematurely exit. + int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent }); + if (waitResult == 1) + { + // The setup wizard set the exit event instead of the start event. Cancel the installation. + throw new InstallCanceledException(); + } + else + { + // Start the installation with a silenced internal UI. + // This "embedded external UI" will handle message types except for source resolution. + internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly; + return true; + } + } + + /// + /// Processes information and progress messages sent to the user interface. + /// + /// Message type. + /// Record that contains message data. + /// Message box buttons. + /// Message box icon. + /// Message box default button. + /// Result of processing the message. + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) + { + // Synchronously send the message to the setup wizard window on its thread. + object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send, + new Func(delegate() + { + return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton); + })); + return (MessageResult) result; + } + + /// + /// Shuts down the embedded UI at the end of the installation. + /// + /// + /// If the installation was canceled during initialization, this method will not be called. + /// If the installation was canceled or failed at any later point, this method will be called at the end. + /// + public void Shutdown() + { + // Wait for the user to exit the setup wizard. + this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal, + new Action(delegate() + { + this.setupWizard.EnableExit(); + })); + this.appThread.Join(); + } + + /// + /// Creates the setup wizard and runs the application thread. + /// + private void Run() + { + this.app = new Application(); + this.setupWizard = new SetupWizard(this.installStartEvent); + this.setupWizard.InitializeComponent(); + this.app.Run(this.setupWizard); + this.installExitEvent.Set(); + } + } +} diff --git a/src/test/dtf/EmbeddedUI/SetupWizard.xaml b/src/test/dtf/EmbeddedUI/SetupWizard.xaml new file mode 100644 index 00000000..9fd493a7 --- /dev/null +++ b/src/test/dtf/EmbeddedUI/SetupWizard.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs new file mode 100644 index 00000000..b846d61f --- /dev/null +++ b/src/test/dtf/EmbeddedUI/SetupWizard.xaml.cs @@ -0,0 +1,109 @@ +namespace WixToolset.Samples.EmbeddedUI +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Navigation; + using System.Windows.Shapes; + using WixToolset.Dtf.WindowsInstaller; + + /// + /// Interaction logic for SetupWizard.xaml + /// + public partial class SetupWizard : Window + { + private ManualResetEvent installStartEvent; + private InstallProgressCounter progressCounter; + private bool canceled; + + public SetupWizard(ManualResetEvent installStartEvent) + { + this.installStartEvent = installStartEvent; + this.progressCounter = new InstallProgressCounter(0.5); + } + + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) + { + try + { + this.progressCounter.ProcessMessage(messageType, messageRecord); + this.progressBar.Value = this.progressBar.Minimum + + this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum); + this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%"; + + switch (messageType) + { + case InstallMessage.Error: + case InstallMessage.Warning: + case InstallMessage.Info: + string message = String.Format("{0}: {1}", messageType, messageRecord); + this.LogMessage(message); + break; + } + + if (this.canceled) + { + this.canceled = false; + return MessageResult.Cancel; + } + } + catch (Exception ex) + { + this.LogMessage(ex.ToString()); + this.LogMessage(ex.StackTrace); + } + + return MessageResult.OK; + } + + private void LogMessage(string message) + { + this.messagesTextBox.Text += Environment.NewLine + message; + this.messagesTextBox.ScrollToEnd(); + } + + internal void EnableExit() + { + this.progressBar.Visibility = Visibility.Hidden; + this.progressLabel.Visibility = Visibility.Hidden; + this.cancelButton.Visibility = Visibility.Hidden; + this.exitButton.Visibility = Visibility.Visible; + } + + private void installButton_Click(object sender, RoutedEventArgs e) + { + this.installButton.Visibility = Visibility.Hidden; + this.progressBar.Visibility = Visibility.Visible; + this.progressLabel.Visibility = Visibility.Visible; + this.installStartEvent.Set(); + } + + private void exitButton_Click(object sender, RoutedEventArgs e) + { + this.Close(); + } + + private void cancelButton_Click(object sender, RoutedEventArgs e) + { + if (this.installButton.Visibility == Visibility.Visible) + { + this.Close(); + } + else + { + this.canceled = true; + this.cancelButton.IsEnabled = false; + } + } + } +} diff --git a/src/test/dtf/SampleCA/SampleCA.cs b/src/test/dtf/SampleCA/SampleCA.cs new file mode 100644 index 00000000..fc9f30fe --- /dev/null +++ b/src/test/dtf/SampleCA/SampleCA.cs @@ -0,0 +1,125 @@ +namespace WixToolset.Samples +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Dtf.WindowsInstaller; + + public class SampleCA + { + [CustomAction] + public static ActionResult SampleCA1(Session session) + { + using (Record msgRec = new Record(0)) + { + msgRec[0] = "Hello from SampleCA1!" + + "\r\nCLR version is v" + Environment.Version; + session.Message(InstallMessage.Info, msgRec); + session.Message(InstallMessage.User, msgRec); + } + + session.Log("Testing summary info..."); + SummaryInfo summInfo = session.Database.SummaryInfo; + session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber); + session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime); + + string testProp = session["SampleCATest"]; + session.Log("Simple property test: [SampleCATest]={0}.", testProp); + + session.Log("Testing subdirectory extraction..."); + string testFilePath = "testsub\\SampleCAs.cs"; + if (!File.Exists(testFilePath)) + { + session.Log("Subdirectory extraction failed. File not found: " + testFilePath); + return ActionResult.Failure; + } + else + { + session.Log("Found file extracted in subdirectory."); + } + + session.Log("Testing record stream extraction..."); + string tempFile = null; + try + { + tempFile = Path.GetTempFileName(); + using (View binView = session.Database.OpenView( + "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " + + "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " + + "`CustomAction`.`Source` = `Binary`.`Name`")) + { + binView.Execute(); + using (Record binRec = binView.Fetch()) + { + binRec.GetStream(1, tempFile); + } + } + + session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length); + string binFileVersion = Installer.GetFileVersion(tempFile); + session.Log("CA binary file version: {0}", binFileVersion); + } + finally + { + if (tempFile != null && File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + + session.Log("Testing record stream reading..."); + using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'")) + { + binView2.Execute(); + using (Record binRec2 = binView2.Fetch()) + { + Stream stream = binRec2.GetStream("Data"); + string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd(); + session.Log("Test data: " + testData); + } + } + + session.Log("Listing components"); + using (View compView = session.Database.OpenView( + "SELECT `Component` FROM `Component`")) + { + compView.Execute(); + foreach (Record compRec in compView) + { + using (compRec) + { + session.Log("\t{0}", compRec["Component"]); + } + } + } + + session.Log("Testing the ability to access an external MSI database..."); + string tempDbFile = Path.GetTempFileName(); + using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect)) + { + // Just create an empty database. + } + using (Database tempDb2 = new Database(tempDbFile)) + { + // See if we can open and query the database. + IList tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); + session.Log("Found " + tables.Count + " tables in the newly created database."); + } + File.Delete(tempDbFile); + + return ActionResult.Success; + } + + [CustomAction("SampleCA2")] + public static ActionResult SampleCustomAction2(Session session) + { + using (Record msgRec = new Record(0)) + { + msgRec[0] = "Hello from SampleCA2!"; + session.Message(InstallMessage.Info, msgRec); + session.Message(InstallMessage.User, msgRec); + } + return ActionResult.UserExit; + } + } +} diff --git a/src/test/dtf/SampleCA/SampleCA.csproj b/src/test/dtf/SampleCA/SampleCA.csproj new file mode 100644 index 00000000..fb6d8dca --- /dev/null +++ b/src/test/dtf/SampleCA/SampleCA.csproj @@ -0,0 +1,10 @@ + + + net472 + Sample managed custom actions + + + + + + diff --git a/src/test/test.cmd b/src/test/test.cmd index 3158b2c2..4c80ba7d 100644 --- a/src/test/test.cmd +++ b/src/test/test.cmd @@ -13,6 +13,8 @@ @call burn\test_burn.cmd %_C% %_T% || exit /b +msbuild -t:Restore dtf\DtfE2ETests.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:%_L%\dtfe2etests.binlog || exit /b + dotnet test wix -c %_C% --nologo -l "trx;LogFileName=%_L%\TestResults\WixToolsetTest.WixE2ETests.trx" || exit /b @popd diff --git a/src/wix/WixToolset.Sdk/WixToolset.Sdk.csproj b/src/wix/WixToolset.Sdk/WixToolset.Sdk.csproj index f2549605..bab6e9b7 100644 --- a/src/wix/WixToolset.Sdk/WixToolset.Sdk.csproj +++ b/src/wix/WixToolset.Sdk/WixToolset.Sdk.csproj @@ -15,7 +15,6 @@ - diff --git a/src/wix/WixToolset.Sdk/tools/wix.ca.targets b/src/wix/WixToolset.Sdk/tools/wix.ca.targets deleted file mode 100644 index 4578c2d8..00000000 --- a/src/wix/WixToolset.Sdk/tools/wix.ca.targets +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - true - - $(TargetName).CA$(TargetExt) - - $(MSBuildThisFileDirectory) - $(WixSdkPath)x86\ - $(WixSdkPath)x64\ - - $(WixSdkPath)MakeSfxCA.exe - $(WixSdkX64Path)SfxCA.dll - $(WixSdkX86Path)SfxCA.dll - - - - - - - - - - - - - - - - - - - @(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.2.3-55-g6feb