aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-05-11 07:43:16 -0700
committerRob Mensching <rob@firegiant.com>2021-05-11 07:43:16 -0700
commit459225b8ad2b11d4cd576f037e459bfb21b5a87a (patch)
tree78ea69e5662dfb63838b8bbbada4cf4a05ebd172 /src
parentafc20ecb53ad3ade524aceefa30d98a3d84fd479 (diff)
parentaa0bd9f66dabc6460f93cf9a029e06b079f10db8 (diff)
downloadwix-459225b8ad2b11d4cd576f037e459bfb21b5a87a.tar.gz
wix-459225b8ad2b11d4cd576f037e459bfb21b5a87a.tar.bz2
wix-459225b8ad2b11d4cd576f037e459bfb21b5a87a.zip
Merge Core.Native
Diffstat (limited to 'src')
-rw-r--r--src/Directory.Build.props30
-rw-r--r--src/Directory.Build.targets109
-rw-r--r--src/Directory.csproj.props13
-rw-r--r--src/Directory.csproj.targets13
-rw-r--r--src/Directory.vcxproj.props118
-rw-r--r--src/Directory.vcxproj.targets45
-rw-r--r--src/ver.rc55
-rw-r--r--src/version.txt1
-rw-r--r--src/wix/README-CoreNative.md2
-rw-r--r--src/wix/WixToolset.Core.Native.sln63
-rw-r--r--src/wix/WixToolset.Core.Native.v3.ncrunchsolution6
-rw-r--r--src/wix/WixToolset.Core.Native/AssemblyExtensions.cs70
-rw-r--r--src/wix/WixToolset.Core.Native/Cabinet.cs120
-rw-r--r--src/wix/WixToolset.Core.Native/CabinetCompressFile.cs65
-rw-r--r--src/wix/WixToolset.Core.Native/CabinetFileInfo.cs63
-rw-r--r--src/wix/WixToolset.Core.Native/DateTimeInterop.cs48
-rw-r--r--src/wix/WixToolset.Core.Native/FileSystem.cs78
-rw-r--r--src/wix/WixToolset.Core.Native/IWindowsInstallerValidatorCallback.cs27
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Database.cs262
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/InstallLogModes.cs111
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/InstallMessage.cs88
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/InstallUILevels.cs72
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Installer.cs138
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/MSIFILEHASHINFO.cs34
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/ModifyView.cs75
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/MsiException.cs77
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/MsiHandle.cs117
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/MsiInterop.cs408
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs40
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Record.cs181
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/Session.cs42
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs376
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/TransformErrorConditions.cs58
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/TransformValidations.cs73
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/View.cs206
-rw-r--r--src/wix/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs49
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/ConfigurationCallback.cs90
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs32
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/IMsmError.cs77
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/IMsmErrors.cs32
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/IMsmMerge2.cs174
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/IMsmStrings.cs32
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/MsmErrorType.cs154
-rw-r--r--src/wix/WixToolset.Core.Native/Msm/MsmInterop.cs49
-rw-r--r--src/wix/WixToolset.Core.Native/Ole32/Storage.cs377
-rw-r--r--src/wix/WixToolset.Core.Native/Ole32/StorageMode.cs55
-rw-r--r--src/wix/WixToolset.Core.Native/PatchAPI/PatchInterop.cs990
-rw-r--r--src/wix/WixToolset.Core.Native/ValidationMessage.cs47
-rw-r--r--src/wix/WixToolset.Core.Native/ValidationMessageType.cs31
-rw-r--r--src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs427
-rw-r--r--src/wix/WixToolset.Core.Native/WixNativeExe.cs125
-rw-r--r--src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj49
-rw-r--r--src/wix/WixToolset.Core.Native/WixToolset.Core.Native.nuspec41
-rw-r--r--src/wix/WixToolset.Core.Native/cubes/darice.cubbin0 -> 684032 bytes
-rw-r--r--src/wix/WixToolset.Core.Native/cubes/mergemod.cubbin0 -> 483328 bytes
-rw-r--r--src/wix/WixToolset.Core.Native/targets/WixToolset.Core.Native.targets9
-rw-r--r--src/wix/appveyor-CoreNative.cmd19
-rw-r--r--src/wix/appveyor-CoreNative.yml44
-rw-r--r--src/wix/nuget-CoreNative.config9
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs96
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/MsmFixture.cs17
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/TestData/test.cabbin0 -> 115 bytes
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/TestData/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/Utility/DisposableFileSystem.cs86
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/Utility/Pushd.cs46
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/Utility/TestData.cs17
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj26
-rw-r--r--src/wix/test/version.txt1
-rw-r--r--src/wix/wixnative/ARM/mergemod.dllbin0 -> 172704 bytes
-rw-r--r--src/wix/wixnative/ARM64/mergemod.dllbin0 -> 194512 bytes
-rw-r--r--src/wix/wixnative/Win32/mergemod.dllbin0 -> 159176 bytes
-rw-r--r--src/wix/wixnative/enumcab.cpp47
-rw-r--r--src/wix/wixnative/extractcab.cpp50
-rw-r--r--src/wix/wixnative/packages.config8
-rw-r--r--src/wix/wixnative/precomp.cpp3
-rw-r--r--src/wix/wixnative/precomp.h19
-rw-r--r--src/wix/wixnative/resetacls.cpp51
-rw-r--r--src/wix/wixnative/smartcab.cpp160
-rw-r--r--src/wix/wixnative/wixnative.cpp38
-rw-r--r--src/wix/wixnative/wixnative.v3.ncrunchproject5
-rw-r--r--src/wix/wixnative/wixnative.vcxproj76
-rw-r--r--src/wix/wixnative/x64/mergemod.dllbin0 -> 180376 bytes
82 files changed, 6943 insertions, 0 deletions
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 00000000..dc78f888
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10 <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11
12 <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
13 <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
14 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
15 <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
16
17 <Authors>WiX Toolset Team</Authors>
18 <Company>WiX Toolset</Company>
19 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
20 <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
21 <Product>WiX Toolset</Product>
22
23 <GitThisAssembly>false</GitThisAssembly>
24 <GitVersionFile>version.txt</GitVersionFile>
25 <GitBaseVersionRegex Condition="'$(GitBaseVersionRegex)' == ''">v?(?&lt;MAJOR&gt;\d+|{[\dA-za-z\-\.]+})\.(?&lt;MINOR&gt;(\d+|{[\dA-za-z\-\.]+}))(?:\-(?&lt;LABEL&gt;[\dA-Za-z\-\.{}]+))?$|^v?(?&lt;MAJOR&gt;\d+|{[\dA-za-z\-\.]+})\.(?&lt;MINOR&gt;(\d+|{[\dA-za-z\-\.]+}))\.(?&lt;PATCH&gt;\d+|{[\dA-za-z\-\.]+})(?:\-(?&lt;LABEL&gt;[\dA-Za-z\-\.{}]+))?$|^(?&lt;LABEL&gt;[\dA-Za-z\-\.{}]+)\-v?(?&lt;MAJOR&gt;\d+|{[\dA-za-z\-\.]+})\.(?&lt;MINOR&gt;\d+|{[\dA-za-z\-\.]+})\.(?&lt;PATCH&gt;\d+|{[\dA-za-z\-\.]+})$</GitBaseVersionRegex>
26 </PropertyGroup>
27
28 <Import Project="Directory$(MSBuildProjectExtension).props" Condition=" Exists('Directory$(MSBuildProjectExtension).props') " />
29 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
30</Project>
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
new file mode 100644
index 00000000..c426f25e
--- /dev/null
+++ b/src/Directory.Build.targets
@@ -0,0 +1,109 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <SigningToolFolder>$(BaseOutputPath)obj\.tools</SigningToolFolder>
10 <SigningToolExe>$(SigningToolFolder)\SignClient.exe</SigningToolExe>
11 <SigningFilelist>$(SigningToolFolder)\empty-filelist.txt</SigningFilelist>
12 <SigningConfiguration>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), signing.json))\signing.json</SigningConfiguration>
13 </PropertyGroup>
14
15 <Target Name="__FixupGitBaseVersion" BeforeTargets="_GitPopulateVersionInfo">
16 <PropertyGroup>
17 <GitBaseVersion>$(GitBaseVersion.Replace('{apiversion}', '$(ApiVersion)'))</GitBaseVersion>
18 <GitBaseVersion>$(GitBaseVersion.Replace('{height}', '$(GitCommits)'))</GitBaseVersion>
19 <GitBaseVersion>$(GitBaseVersion.Replace('{commits}', '$(GitCommits)'))</GitBaseVersion>
20 </PropertyGroup>
21 </Target>
22
23 <Target Name="__SetPropertiesFromGit" DependsOnTargets="GitVersion">
24 <PropertyGroup>
25 <AssemblyVersion>$(GitBaseVersionMajor).$(GitBaseVersionMinor).0.0</AssemblyVersion>
26 <FileVersion>$(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch).$(GitCommits)</FileVersion>
27 <PackageVersion>$(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel)</PackageVersion>
28 <InformationalVersion>$(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel)+$(GitSha)</InformationalVersion>
29 <!-- We already included the $(GitSha) in the informational version so do not include it again. -->
30 <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
31 </PropertyGroup>
32
33 <Message Importance="$(GitInfoReportImportance)" Text="Properties from Git:
34 GitCommits: $(GitCommits)
35 AssemblyVersion: $(AssemblyVersion)
36 FileVersion: $(FileVersion)
37 InformationalVersion: $(InformationalVersion)
38 PackageVersion: $(PackageVersion)
39" />
40 </Target>
41
42 <PropertyGroup>
43 <GenerateNuspecDependsOn>
44 $(GenerateNuspecDependsOn);
45 __SetNuspecProperties
46 </GenerateNuspecDependsOn>
47 </PropertyGroup>
48
49 <Target Name="__SetNuspecProperties" DependsOnTargets="__SetPropertiesFromGit"
50 Condition=" Exists('$(MSBuildProjectName).nuspec') ">
51 <PropertyGroup>
52 <ProjectUrl Condition=" '$(ProjectUrl)'=='' and '$(GitRepositoryUrl)'!='' ">$(GitRepositoryUrl.Replace('.git',''))</ProjectUrl>
53
54 <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
55 <NuspecBasePath Condition=" '$(NuspecBasePath)'=='' ">$([System.IO.Path]::GetFullPath($(OutputPath)..))\</NuspecBasePath>
56 <NuspecProperties>$(NuspecProperties);Id=$(PackageId);Authors="$(Authors)";Configuration=$(Configuration);Copyright="$(Copyright)";Description="$(Description)";Title="$(Title)"</NuspecProperties>
57 <NuspecProperties>$(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(GitSha);RepositoryType=git;RepositoryUrl=$(GitRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl)</NuspecProperties>
58 <PublishRepositoryUrl>true</PublishRepositoryUrl>
59 <SymbolPackageFormat>snupkg</SymbolPackageFormat>
60 </PropertyGroup>
61
62 <Message Importance="$(GitInfoReportImportance)" Text="NuSpec info:
63 NuspecFile: $(NuspecFile)
64 NuspecBasePath: $(NuspecBasePath)
65 NuspecProperties: $(NuspecProperties)
66" />
67
68 </Target>
69
70 <Target Name="PackNative" DependsOnTargets="__SetNuspecProperties"
71 Condition=" Exists('$(MSBuildProjectName).nuspec') ">
72
73 <Exec Command='nuget pack $(NuspecFile) -OutputDirectory "$(BaseOutputPath)$(Configuration)" -BasePath "$(NuspecBasePath)" -Properties $(NuspecProperties)'
74 WorkingDirectory="$(MSBuildProjectDirectory)" />
75
76 <ItemGroup>
77 <NuGetPackOutput Include="$(BaseOutputPath)$(Configuration)\**\$(PackageId)*.nupkg" />
78 </ItemGroup>
79 </Target>
80
81 <Target Name="_GetSignClient"
82 Condition=" !Exists('$(SigningToolExe)') ">
83
84 <WriteLinesToFile File='$(SigningFilelist)' Lines='do-not-sign-files-in-nupkg' Overwrite='true' />
85
86 <Exec Command='dotnet.exe tool install --tool-path "$(SigningToolFolder)" SignClient' />
87 </Target>
88
89 <Target Name="SignOutput" DependsOnTargets="_GetSignClient" AfterTargets="AfterBuild"
90 Condition=" '$(SigningUser)'!='' and '$(SignOutput)'!='false' and
91 ('$(MSBuildProjectExtension)'=='.csproj' or ('$(MSBuildProjectExtension)'=='.vcxproj' and '$(ConfigurationType)'!='StaticLibrary'))">
92
93 <Exec Command='"$(SigningToolExe)" sign -i $(TargetPath) -c "$(SigningConfiguration)" -n "WiX Toolset" -d "WiX Toolset" -u https://wixtoolset.org/ -r "$(SigningUser)" -s "$(SigningSecret)"'
94 WorkingDirectory="$(MSBuildProjectDirectory)" EchoOff="true" />
95 </Target>
96
97 <Target Name="SignNupkg" DependsOnTargets="_GetSignClient" AfterTargets="Pack;PackNative"
98 Condition=" '$(SigningUser)'!='' and '@(NuGetPackOutput)'!='' and '$(SignNupkg)'!='false' ">
99 <ItemGroup>
100 <SigningNupkgs Include="@(NuGetPackOutput)" Condition=" '%(Extension)'=='.nupkg' " />
101 </ItemGroup>
102
103 <Exec Command='"$(SigningToolExe)" sign -i "@(SigningNupkgs->&apos;%(Identity)&apos;)" -c "$(SigningConfiguration)" -f "$(SigningFilelist)" -n "WiX Toolset" -d "WiX Toolset" -u https://wixtoolset.org/ -r "$(SigningUser)" -s "$(SigningSecret)"'
104 WorkingDirectory="$(MSBuildProjectDirectory)" EchoOff="true" />
105 </Target>
106
107 <Import Project="Directory$(MSBuildProjectExtension).targets" Condition=" Exists('Directory$(MSBuildProjectExtension).targets') " />
108 <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
109</Project>
diff --git a/src/Directory.csproj.props b/src/Directory.csproj.props
new file mode 100644
index 00000000..81d24ad1
--- /dev/null
+++ b/src/Directory.csproj.props
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
9 <SignAssembly>true</SignAssembly>
10 <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile>
11 <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass>
12 </PropertyGroup>
13</Project>
diff --git a/src/Directory.csproj.targets b/src/Directory.csproj.targets
new file mode 100644
index 00000000..49303a1d
--- /dev/null
+++ b/src/Directory.csproj.targets
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.csproj.targets
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
9 <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
10 </PropertyGroup>
11
12 <Target Name="__SetAssemblyInfoFromGit" DependsOnTargets="__SetPropertiesFromGit" BeforeTargets="GetAssemblyVersion" />
13</Project>
diff --git a/src/Directory.vcxproj.props b/src/Directory.vcxproj.props
new file mode 100644
index 00000000..63d73b36
--- /dev/null
+++ b/src/Directory.vcxproj.props
@@ -0,0 +1,118 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project>
5 <PropertyGroup>
6 <Platform Condition=" '$(Platform)' == '' OR '$(Platform)' == 'AnyCPU' ">Win32</Platform>
7 <IntDir>$(BaseIntermediateOutputPath)$(Configuration)\$(Platform)\</IntDir>
8 <OutDir>$(OutputPath)$(Platform)\</OutDir>
9
10 <!-- NBGV properties -->
11 <AssemblyCompany>$(Company)</AssemblyCompany>
12 <AssemblyCopyright>$(Copyright)</AssemblyCopyright>
13
14 <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
15 <NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
16 </PropertyGroup>
17
18 <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' AND '$(VisualStudioVersion)'>='15.0'">
19 <WindowsTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</WindowsTargetPlatformVersion>
20 </PropertyGroup>
21
22 <PropertyGroup>
23 <CodeAnalysisRuleSet Condition=" Exists('$(MSBuildThisFileDirectory)CustomizedNativeRecommendedRules.ruleset') ">$(MSBuildThisFileDirectory)CustomizedNativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
24 </PropertyGroup>
25
26 <ItemDefinitionGroup>
27 <ClCompile>
28 <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
29 <WarningLevel>Level4</WarningLevel>
30 <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
31 <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0600;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
32 <PrecompiledHeader>Use</PrecompiledHeader>
33 <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
34 <CallingConvention Condition="'$(Platform)'=='Win32'">StdCall</CallingConvention>
35 <TreatWarningAsError>true</TreatWarningAsError>
36 <ExceptionHandling>false</ExceptionHandling>
37 <ControlFlowGuard>Guard</ControlFlowGuard>
38 <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
39 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
40 <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
41 </ClCompile>
42 <ResourceCompile>
43 <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
44 <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
45 </ResourceCompile>
46 <Lib>
47 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
48 </Lib>
49 <Link>
50 <SubSystem>$(ProjectSubSystem)</SubSystem>
51 <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
52 <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
53 <GenerateDebugInformation>true</GenerateDebugInformation>
54 <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
55 <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
56 <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
57 </Link>
58 </ItemDefinitionGroup>
59
60 <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
61 <ClCompile>
62 <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
63 </ClCompile>
64 </ItemDefinitionGroup>
65 <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
66 <ClCompile>
67 <CallingConvention>CDecl</CallingConvention>
68 </ClCompile>
69 </ItemDefinitionGroup>
70 <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
71 <ClCompile>
72 <DebugInformationFormat>OldStyle</DebugInformationFormat>
73 <OmitDefaultLibName>true</OmitDefaultLibName>
74 <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
75 </ClCompile>
76 </ItemDefinitionGroup>
77 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
78 <ClCompile>
79 <Optimization>Disabled</Optimization>
80 <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
81 <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
82 <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
83 </ClCompile>
84 </ItemDefinitionGroup>
85 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
86 <ClCompile>
87 <ControlFlowGuard></ControlFlowGuard>
88 <BasicRuntimeChecks></BasicRuntimeChecks>
89 <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
90 </ClCompile>
91 </ItemDefinitionGroup>
92 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
93 <ClCompile>
94 <Optimization>MinSpace</Optimization>
95 <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
96 <FunctionLevelLinking>true</FunctionLevelLinking>
97 <IntrinsicFunctions>true</IntrinsicFunctions>
98 <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
99 </ClCompile>
100 <Link>
101 <EnableCOMDATFolding>true</EnableCOMDATFolding>
102 <OptimizeReferences>true</OptimizeReferences>
103 </Link>
104 </ItemDefinitionGroup>
105 <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
106 <ClCompile>
107 <ControlFlowGuard></ControlFlowGuard>
108 <BasicRuntimeChecks></BasicRuntimeChecks>
109 <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
110 </ClCompile>
111 </ItemDefinitionGroup>
112 <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
113 <Link>
114 <KeyFile>$(LinkKeyFile)</KeyFile>
115 <DelaySign>$(LinkDelaySign)</DelaySign>
116 </Link>
117 </ItemDefinitionGroup>
118</Project>
diff --git a/src/Directory.vcxproj.targets b/src/Directory.vcxproj.targets
new file mode 100644
index 00000000..9f0689d5
--- /dev/null
+++ b/src/Directory.vcxproj.targets
@@ -0,0 +1,45 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.csproj.targets
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <PrepareForBuildDependsOn>
9 $(PrepareForBuildDependsOn);
10 __SetVersionResource
11 </PrepareForBuildDependsOn>
12 </PropertyGroup>
13
14 <Target Name="__SetVersionResource" DependsOnTargets="__SetPropertiesFromGit">
15 <PropertyGroup>
16 <_ResourceFileType Condition=" '$(ConfigurationType)'=='Application' ">VFT_APP</_ResourceFileType>
17 <_ResourceFileType Condition=" '$(ConfigurationType)'=='DynamicLibrary' ">VFT_DLL</_ResourceFileType>
18 <Description Condition=" '$(Description)'=='' ">$(TargetName)</Description>
19 <Title Condition=" '$(Title)'=='' ">$(Description)</Title>
20 </PropertyGroup>
21
22 <ItemGroup>
23 <ResourceCompile Include="$(MSBuildThisFileDirectory)ver.rc">
24 <PreprocessorDefinitions>
25 %(PreprocessorDefinitions);
26 GITVER_FILEVER=$(FileVersion.Replace('.', ','));
27 GITVER_PRODUCTVER=$(AssemblyVersion.Replace('.', ','));
28 GITVER_FILE_VERSION=$(FileVersion);
29 GITVER_PRODUCT_VERSION=$(AssemblyVersion);
30 GITVER_FILE_TYPE=$(_ResourceFileType);
31 GITVER_CODEPAGE=0;
32 GITVER_LCID=$([System.Convert]::ToInt32('%(Culture)', 16));
33 GITVER_VERSION_BLOCK=$([System.Convert]::ToString($([MSBuild]::Multiply($([System.Convert]::ToUint64('%(Culture)', 16)), 65536)), 16).PadLeft(8, '0'));
34 GITVER_COMPANY=$(Company);
35 GITVER_COPYRIGHT=$(Copyright);
36 GITVER_TITLE=$(Title);
37 GITVER_PRODUCT=$(Product);
38 GITVER_INFORMATIONAL_VERSION=$(InformationalVersion);
39 GITVER_INTERNAL_NAME=$(TargetName);
40 GITVER_FILE_NAME=$(TargetFileName);
41 </PreprocessorDefinitions>
42 </ResourceCompile>
43 </ItemGroup>
44 </Target>
45</Project>
diff --git a/src/ver.rc b/src/ver.rc
new file mode 100644
index 00000000..4dc64d37
--- /dev/null
+++ b/src/ver.rc
@@ -0,0 +1,55 @@
1#pragma once
2// ------------------------------------------------------------------------------
3// <auto-generated>
4// This code was generated by a tool.
5// Runtime Version:4.0.30319.42000
6// Changes to this file may cause incorrect behavior and will be lost if
7// the code is regenerated.
8// </auto-generated>
9// ------------------------------------------------------------------------------
10
11#if defined(_UNICODE)
12#define GITVER_VERSION_STRING(x) L ## #x
13#else
14#define GITVER_VERSION_STRING(x) #x
15#endif
16
17#define GVS(x) GITVER_VERSION_STRING(x)
18
19#ifdef RC_INVOKED
20
21#include <winres.h>
22
23VS_VERSION_INFO VERSIONINFO
24 FILEVERSION GITVER_FILEVER
25 PRODUCTVERSION GITVER_PRODUCTVER
26 FILEFLAGSMASK 0x3FL
27#ifdef _DEBUG
28 FILEFLAGS 0x1L
29#else
30 FILEFLAGS 0x0L
31#endif
32 FILEOS 0x4L
33 FILETYPE GITVER_FILE_TYPE
34 FILESUBTYPE 0x0L
35BEGIN
36 BLOCK "StringFileInfo"
37 BEGIN
38 BLOCK GVS(GITVER_VERSION_BLOCK)
39 BEGIN
40 VALUE "CompanyName", GVS(GITVER_COMPANY)
41 VALUE "FileDescription", GVS(GITVER_TITLE)
42 VALUE "FileVersion", GVS(GITVER_FILE_VERSION)
43 VALUE "InternalName", GVS(GITVER_INTERNAL_NAME)
44 VALUE "OriginalFilename", GVS(GITVER_FILE_NAME)
45 VALUE "ProductName", GVS(GITVER_PRODUCT)
46 VALUE "ProductVersion", GVS(GITVER_INFORMATIONAL_VERSION)
47 VALUE "LegalCopyright", GVS(GITVER_COPYRIGHT)
48 END
49 END
50 BLOCK "VarFileInfo"
51 BEGIN
52 VALUE "Translation", GITVER_LCID, GITVER_CODEPAGE
53 END
54END
55#endif
diff --git a/src/version.txt b/src/version.txt
new file mode 100644
index 00000000..dc60d914
--- /dev/null
+++ b/src/version.txt
@@ -0,0 +1 @@
v4.{apiversion}-preview.0-build.{height}
diff --git a/src/wix/README-CoreNative.md b/src/wix/README-CoreNative.md
new file mode 100644
index 00000000..787123cc
--- /dev/null
+++ b/src/wix/README-CoreNative.md
@@ -0,0 +1,2 @@
1# Core.Native
2Core.Native - native component of WixToolset.Core
diff --git a/src/wix/WixToolset.Core.Native.sln b/src/wix/WixToolset.Core.Native.sln
new file mode 100644
index 00000000..0d7a5921
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native.sln
@@ -0,0 +1,63 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio 15
4VisualStudioVersion = 15.0.27004.2009
5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Native", "src\WixToolset.Core.Native\WixToolset.Core.Native.csproj", "{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}"
7EndProject
8Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wixnative", "src\wixnative\wixnative.vcxproj", "{8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}"
9EndProject
10Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Native", "src\test\WixToolsetTest.Core.Native\WixToolsetTest.Core.Native.csproj", "{54F5329A-C113-471A-8EE1-83021E8A4853}"
11EndProject
12Global
13 GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 Debug|Any CPU = Debug|Any CPU
15 Debug|x64 = Debug|x64
16 Debug|x86 = Debug|x86
17 Release|Any CPU = Release|Any CPU
18 Release|x64 = Release|x64
19 Release|x86 = Release|x86
20 EndGlobalSection
21 GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x64.ActiveCfg = Debug|Any CPU
25 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x64.Build.0 = Debug|Any CPU
26 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x86.ActiveCfg = Debug|Any CPU
27 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x86.Build.0 = Debug|Any CPU
28 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|Any CPU.Build.0 = Release|Any CPU
30 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x64.ActiveCfg = Release|Any CPU
31 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x64.Build.0 = Release|Any CPU
32 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x86.ActiveCfg = Release|Any CPU
33 {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x86.Build.0 = Release|Any CPU
34 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|Any CPU.ActiveCfg = Debug|Win32
35 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x64.ActiveCfg = Debug|x64
36 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x64.Build.0 = Debug|x64
37 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x86.ActiveCfg = Debug|Win32
38 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x86.Build.0 = Debug|Win32
39 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|Any CPU.ActiveCfg = Release|Win32
40 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x64.ActiveCfg = Release|x64
41 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x64.Build.0 = Release|x64
42 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x86.ActiveCfg = Release|Win32
43 {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x86.Build.0 = Release|Win32
44 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x64.ActiveCfg = Debug|Any CPU
47 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x64.Build.0 = Debug|Any CPU
48 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x86.ActiveCfg = Debug|Any CPU
49 {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x86.Build.0 = Debug|Any CPU
50 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|Any CPU.Build.0 = Release|Any CPU
52 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x64.ActiveCfg = Release|Any CPU
53 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x64.Build.0 = Release|Any CPU
54 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x86.ActiveCfg = Release|Any CPU
55 {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x86.Build.0 = Release|Any CPU
56 EndGlobalSection
57 GlobalSection(SolutionProperties) = preSolution
58 HideSolutionNode = FALSE
59 EndGlobalSection
60 GlobalSection(ExtensibilityGlobals) = postSolution
61 SolutionGuid = {1E952530-A3ED-4E65-AF39-9025EFB85322}
62 EndGlobalSection
63EndGlobal
diff --git a/src/wix/WixToolset.Core.Native.v3.ncrunchsolution b/src/wix/WixToolset.Core.Native.v3.ncrunchsolution
new file mode 100644
index 00000000..10420ac9
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native.v3.ncrunchsolution
@@ -0,0 +1,6 @@
1<SolutionConfiguration>
2 <Settings>
3 <AllowParallelTestExecution>True</AllowParallelTestExecution>
4 <SolutionConfigured>True</SolutionConfigured>
5 </Settings>
6</SolutionConfiguration> \ No newline at end of file
diff --git a/src/wix/WixToolset.Core.Native/AssemblyExtensions.cs b/src/wix/WixToolset.Core.Native/AssemblyExtensions.cs
new file mode 100644
index 00000000..590a6887
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/AssemblyExtensions.cs
@@ -0,0 +1,70 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.IO;
7 using System.Reflection;
8 using System.Text;
9
10 internal static class AssemblyExtensions
11 {
12 internal static FindAssemblyRelativeFileResult FindFileRelativeToAssembly(this Assembly assembly, string relativePath, bool searchNativeDllDirectories)
13 {
14 // First try using the Assembly.Location. This works in almost all cases with
15 // no side-effects.
16 var path = Path.Combine(Path.GetDirectoryName(assembly.Location), relativePath);
17 var possiblePaths = new StringBuilder(path);
18
19 var found = File.Exists(path);
20 if (!found)
21 {
22 // Fallback to the Assembly.CodeBase to handle "shadow copy" scenarios (like unit tests) but
23 // only check codebase if it is different from the Assembly.Location path.
24 var codebase = Path.Combine(Path.GetDirectoryName(new Uri(assembly.CodeBase).LocalPath), relativePath);
25
26 if (!codebase.Equals(path, StringComparison.OrdinalIgnoreCase))
27 {
28 path = codebase;
29 possiblePaths.Append(Path.PathSeparator + path);
30
31 found = File.Exists(path);
32 }
33
34 if (!found && searchNativeDllDirectories && AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES") is string searchDirectoriesString)
35 {
36 // If instructed to search native DLL search directories, try to find our file there.
37 possiblePaths.Append(Path.PathSeparator + searchDirectoriesString);
38
39 var searchDirectories = searchDirectoriesString?.Split(Path.PathSeparator);
40 foreach (var directoryPath in searchDirectories)
41 {
42 var possiblePath = Path.Combine(directoryPath, relativePath);
43 if (File.Exists(possiblePath))
44 {
45 path = possiblePath;
46 found = true;
47 break;
48 }
49 }
50 }
51 }
52
53 return new FindAssemblyRelativeFileResult
54 {
55 Found = found,
56 Path = found ? path : null,
57 PossiblePaths = possiblePaths.ToString()
58 };
59 }
60
61 internal class FindAssemblyRelativeFileResult
62 {
63 public bool Found { get; set; }
64
65 public string Path { get; set; }
66
67 public string PossiblePaths { get; set; }
68 }
69 }
70}
diff --git a/src/wix/WixToolset.Core.Native/Cabinet.cs b/src/wix/WixToolset.Core.Native/Cabinet.cs
new file mode 100644
index 00000000..9b77bd37
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Cabinet.cs
@@ -0,0 +1,120 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Cabinet create, enumerate and extract mechanism.
12 /// </summary>
13 public sealed class Cabinet
14 {
15 private const string CompressionLevelVariable = "WIX_COMPRESSION_LEVEL";
16 private static readonly char[] TextLineSplitter = new[] { '\t' };
17
18 /// <summary>
19 /// Creates a cabinet creation, enumeration, extraction mechanism.
20 /// </summary>
21 /// <param name="path">Path of cabinet</param>
22 public Cabinet(string path)
23 {
24 this.Path = path;
25 }
26
27 /// <summary>
28 /// Cabinet path.
29 /// </summary>
30 public string Path { get; }
31
32 /// <summary>
33 /// Creates a cabinet.
34 /// </summary>
35 /// <param name="files">Files to compress.</param>
36 /// <param name="compressionLevel">Level of compression to apply.</param>
37 /// <param name="maxSize">Maximum size of cabinet.</param>
38 /// <param name="maxThresh">Maximum threshold for each cabinet.</param>
39 public void Compress(IEnumerable<CabinetCompressFile> files, CompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0)
40 {
41 var compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable);
42
43 // Override authored compression level if environment variable is present.
44 if (!String.IsNullOrEmpty(compressionLevelVariable))
45 {
46 if (!Enum.TryParse(compressionLevelVariable, true, out compressionLevel))
47 {
48 throw new WixException(ErrorMessages.IllegalEnvironmentVariable(CompressionLevelVariable, compressionLevelVariable));
49 }
50 }
51
52 var wixnative = new WixNativeExe("smartcab", this.Path, Convert.ToInt32(compressionLevel), files.Count(), maxSize, maxThresh);
53
54 foreach (var file in files)
55 {
56 wixnative.AddStdinLine(file.ToWixNativeStdinLine());
57 }
58
59 wixnative.Run();
60
61#if TOOD_ERROR_HANDLING
62 catch (COMException ce)
63 {
64 // If we get a "the file exists" error, we must have a full temp directory - so report the issue
65 if (0x80070050 == unchecked((uint)ce.ErrorCode))
66 {
67 throw new WixException(WixErrors.FullTempDirectory("WSC", Path.GetTempPath()));
68 }
69
70 throw;
71 }
72#endif
73 }
74
75 /// <summary>
76 /// Enumerates all files in a cabinet.
77 /// </summary>
78 /// <returns>>List of CabinetFileInfo</returns>
79 public List<CabinetFileInfo> Enumerate()
80 {
81 var wixnative = new WixNativeExe("enumcab", this.Path);
82 var lines = wixnative.Run();
83
84 var fileInfoList = new List<CabinetFileInfo>();
85
86 foreach (var line in lines)
87 {
88 if (String.IsNullOrEmpty(line))
89 {
90 continue;
91 }
92
93 var data = line.Split(TextLineSplitter, StringSplitOptions.None);
94
95 var size = Convert.ToInt32(data[1]);
96 var date = Convert.ToInt32(data[2]);
97 var time = Convert.ToInt32(data[3]);
98
99 fileInfoList.Add(new CabinetFileInfo(data[0], size, date, time));
100 }
101
102 return fileInfoList;
103 }
104
105 /// <summary>
106 /// Extracts all the files from a cabinet to a directory.
107 /// </summary>
108 /// <param name="outputFolder">Directory to extract files to.</param>
109 public IEnumerable<string> Extract(string outputFolder)
110 {
111 if (!outputFolder.EndsWith("\\", StringComparison.Ordinal))
112 {
113 outputFolder += "\\";
114 }
115
116 var wixnative = new WixNativeExe("extractcab", this.Path, outputFolder);
117 return wixnative.Run().Where(output => !String.IsNullOrWhiteSpace(output));
118 }
119 }
120}
diff --git a/src/wix/WixToolset.Core.Native/CabinetCompressFile.cs b/src/wix/WixToolset.Core.Native/CabinetCompressFile.cs
new file mode 100644
index 00000000..6778f4a1
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/CabinetCompressFile.cs
@@ -0,0 +1,65 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 /// <summary>
6 /// Information to compress file into a cabinet.
7 /// </summary>
8 public sealed class CabinetCompressFile
9 {
10 /// <summary>
11 /// Cabinet compress file.
12 /// </summary>
13 /// <param name="path">Path to file to add.</param>
14 /// <param name="token">The token for the file.</param>
15 public CabinetCompressFile(string path, string token)
16 {
17 this.Path = path;
18 this.Token = token;
19 this.Hash = null;
20 }
21
22 /// <summary>
23 /// Cabinet compress file.
24 /// </summary>
25 /// <param name="path">Path to file to add.</param>
26 /// <param name="token">The token for the file.</param>
27 /// <param name="hash1">Hash 1</param>
28 /// <param name="hash2">Hash 2</param>
29 /// <param name="hash3">Hash 3</param>
30 /// <param name="hash4">Hash 4</param>
31 public CabinetCompressFile(string path, string token, int hash1, int hash2, int hash3, int hash4)
32 {
33 this.Path = path;
34 this.Token = token;
35 this.Hash = new[] { hash1, hash2, hash3, hash4 };
36 }
37
38 /// <summary>
39 /// Gets the path to the file to compress.
40 /// </summary>
41 public string Path { get; }
42
43 /// <summary>
44 /// Gets the token for the file to compress.
45 /// </summary>
46 public string Token { get; }
47
48 /// <summary>
49 /// Gets the hash of the file to compress.
50 /// </summary>
51 public int[] Hash { get; }
52
53 internal string ToWixNativeStdinLine()
54 {
55 if (this.Hash == null)
56 {
57 return $"{this.Path}\t{this.Token}";
58 }
59 else
60 {
61 return $"{this.Path}\t{this.Token}\t{this.Hash[0]}\t{this.Hash[1]}\t{this.Hash[2]}\t{this.Hash[3]}";
62 }
63 }
64 }
65}
diff --git a/src/wix/WixToolset.Core.Native/CabinetFileInfo.cs b/src/wix/WixToolset.Core.Native/CabinetFileInfo.cs
new file mode 100644
index 00000000..07387191
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/CabinetFileInfo.cs
@@ -0,0 +1,63 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6
7 /// <summary>
8 /// Properties of a file in a cabinet.
9 /// </summary>
10 public sealed class CabinetFileInfo
11 {
12 /// <summary>
13 /// Constructs CabinetFileInfo
14 /// </summary>
15 /// <param name="fileId">File Id</param>
16 /// <param name="size">Size of file</param>
17 /// <param name="date">Last modified date</param>
18 /// <param name="time">Last modified time</param>
19 public CabinetFileInfo(string fileId, int size, int date, int time)
20 {
21 this.FileId = fileId;
22 this.Size = size;
23 this.Date = date;
24 this.Time = time;
25 }
26
27 /// <summary>
28 /// Gets the file Id of the file.
29 /// </summary>
30 /// <value>file Id</value>
31 public string FileId { get; }
32
33 /// <summary>
34 /// Gets modified date (DOS format).
35 /// </summary>
36 public int Date { get; }
37
38 /// <summary>
39 /// Gets modified time (DOS format).
40 /// </summary>
41 public int Time { get; }
42
43 /// <summary>
44 /// Gets the size of the file in bytes.
45 /// </summary>
46 public int Size { get; }
47
48 /// <summary>
49 /// Compares this file info's date and time with another datetime.
50 /// </summary>
51 /// <param name="dateTime">Date and time to compare with/</param>
52 /// <returns>
53 /// For some reason DateTime.ToLocalTime() does not match kernel32.dll FileTimeToLocalFileTime().
54 /// Since cabinets store date and time with the kernel32.dll functions, we need to convert DateTime
55 /// to local file time using the kernel32.dll functions.
56 /// </returns>
57 public bool SameAsDateTime(DateTime dateTime)
58 {
59 DateTimeInterop.DateTimeToCabDateAndTime(dateTime, out var cabDate, out var cabTime);
60 return this.Date == cabDate && this.Time == cabTime;
61 }
62 }
63}
diff --git a/src/wix/WixToolset.Core.Native/DateTimeInterop.cs b/src/wix/WixToolset.Core.Native/DateTimeInterop.cs
new file mode 100644
index 00000000..d2a0ba2b
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/DateTimeInterop.cs
@@ -0,0 +1,48 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Interop class for the date/time handling.
10 /// </summary>
11 internal static class DateTimeInterop
12 {
13 /// <summary>
14 /// Converts DateTime to MS-DOS date and time which cabinet uses.
15 /// </summary>
16 /// <param name="dateTime">DateTime</param>
17 /// <param name="cabDate">MS-DOS date</param>
18 /// <param name="cabTime">MS-DOS time</param>
19 public static void DateTimeToCabDateAndTime(DateTime dateTime, out ushort cabDate, out ushort cabTime)
20 {
21 // dateTime.ToLocalTime() does not match FileTimeToLocalFileTime() for some reason.
22 // so we need to call FileTimeToLocalFileTime() from kernel32.dll.
23 long filetime = dateTime.ToFileTime();
24 long localTime = 0;
25 FileTimeToLocalFileTime(ref filetime, ref localTime);
26 FileTimeToDosDateTime(ref localTime, out cabDate, out cabTime);
27 }
28
29 /// <summary>
30 /// Converts file time to a local file time.
31 /// </summary>
32 /// <param name="fileTime">file time</param>
33 /// <param name="localTime">local file time</param>
34 /// <returns>true if successful, false otherwise</returns>
35 [DllImport("kernel32.dll", SetLastError = true)]
36 private static extern bool FileTimeToLocalFileTime(ref long fileTime, ref long localTime);
37
38 /// <summary>
39 /// Converts file time to a MS-DOS time.
40 /// </summary>
41 /// <param name="fileTime">file time</param>
42 /// <param name="wFatDate">MS-DOS date</param>
43 /// <param name="wFatTime">MS-DOS time</param>
44 /// <returns>true if successful, false otherwise</returns>
45 [DllImport("kernel32.dll", SetLastError = true)]
46 private static extern bool FileTimeToDosDateTime(ref long fileTime, out ushort wFatDate, out ushort wFatTime);
47 }
48}
diff --git a/src/wix/WixToolset.Core.Native/FileSystem.cs b/src/wix/WixToolset.Core.Native/FileSystem.cs
new file mode 100644
index 00000000..d843a9e8
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/FileSystem.cs
@@ -0,0 +1,78 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Runtime.InteropServices;
9 using System.Security.AccessControl;
10
11 /// <summary>
12 /// File system helpers.
13 /// </summary>
14 public static class FileSystem
15 {
16 /// <summary>
17 /// Copies a file.
18 /// </summary>
19 /// <param name="source">The file to copy.</param>
20 /// <param name="destination">The destination file.</param>
21 /// <param name="allowHardlink">Allow hardlinks.</param>
22 public static void CopyFile(string source, string destination, bool allowHardlink)
23 {
24 EnsureDirectoryWithoutFile(destination);
25
26 if (!allowHardlink || !CreateHardLink(destination, source, IntPtr.Zero))
27 {
28#if DEBUG
29 var er = Marshal.GetLastWin32Error();
30#endif
31
32 File.Copy(source, destination, overwrite: true);
33 }
34 }
35
36 /// <summary>
37 /// Moves a file.
38 /// </summary>
39 /// <param name="source">The file to move.</param>
40 /// <param name="destination">The destination file.</param>
41 public static void MoveFile(string source, string destination)
42 {
43 EnsureDirectoryWithoutFile(destination);
44
45 File.Move(source, destination);
46 }
47
48 /// <summary>
49 /// Reset the ACLs on a set of files.
50 /// </summary>
51 /// <param name="files">The list of file paths to set ACLs.</param>
52 public static void ResetAcls(IEnumerable<string> files)
53 {
54 var aclReset = new FileSecurity();
55 aclReset.SetAccessRuleProtection(false, false);
56
57 foreach (var file in files)
58 {
59 new FileInfo(file).SetAccessControl(aclReset);
60 }
61 }
62
63 private static void EnsureDirectoryWithoutFile(string path)
64 {
65 File.Delete(path);
66
67 var directory = Path.GetDirectoryName(path);
68
69 if (!String.IsNullOrEmpty(directory))
70 {
71 Directory.CreateDirectory(directory);
72 }
73 }
74
75 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
76 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
77 }
78}
diff --git a/src/wix/WixToolset.Core.Native/IWindowsInstallerValidatorCallback.cs b/src/wix/WixToolset.Core.Native/IWindowsInstallerValidatorCallback.cs
new file mode 100644
index 00000000..f4aff134
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/IWindowsInstallerValidatorCallback.cs
@@ -0,0 +1,27 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 /// <summary>
6 /// Callbacks during validation.
7 /// </summary>
8 public interface IWindowsInstallerValidatorCallback
9 {
10 /// <summary>
11 /// Indicates if the validator callback encountered an error.
12 /// </summary>
13 bool EncounteredError { get; }
14
15 /// <summary>
16 /// Validation blocked by another Windows Installer operation.
17 /// </summary>
18 void ValidationBlocked();
19
20 /// <summary>
21 /// Validation message from an ICE.
22 /// </summary>
23 /// <param name="message">The validation message.</param>
24 /// <returns>True if validation should continue; otherwise cancel the validation.</returns>
25 bool ValidationMessage(ValidationMessage message);
26 }
27}
diff --git a/src/wix/WixToolset.Core.Native/Msi/Database.cs b/src/wix/WixToolset.Core.Native/Msi/Database.cs
new file mode 100644
index 00000000..b9c5c35b
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/Database.cs
@@ -0,0 +1,262 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.IO;
7 using System.Threading;
8
9 /// <summary>
10 /// Wrapper class for managing MSI API database handles.
11 /// </summary>
12 public sealed class Database : MsiHandle
13 {
14 private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021);
15
16 /// <summary>
17 /// Constructor that opens an MSI database.
18 /// </summary>
19 /// <param name="path">Path to the database to be opened.</param>
20 /// <param name="type">Persist mode to use when opening the database.</param>
21 public Database(string path, OpenDatabase type)
22 {
23 var error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out var handle);
24 if (0 != error)
25 {
26 throw new MsiException(error);
27 }
28 this.Handle = handle;
29 }
30
31 /// <summary>
32 /// Maximum length of stream in an MSI database.
33 /// </summary>
34 public static int MsiMaxStreamNameLength => MsiInterop.MsiMaxStreamNameLength;
35
36 /// <summary>
37 /// Apply a transform to the MSI.
38 /// </summary>
39 /// <param name="transformFile">Path to transform to apply.</param>
40 public void ApplyTransform(string transformFile)
41 {
42 // get the curret validation bits
43 var conditions = TransformErrorConditions.None;
44 using (var summaryInfo = new SummaryInformation(transformFile))
45 {
46 try
47 {
48 var validationFlags = summaryInfo.GetNumericProperty(SummaryInformation.Transform.ValidationFlags);
49 conditions = (TransformErrorConditions)(validationFlags & 0xffff);
50 }
51 catch (FormatException)
52 {
53 // fallback to default of None
54 }
55 }
56
57 this.ApplyTransform(transformFile, conditions);
58 }
59
60 /// <summary>
61 /// Applies a transform to this database.
62 /// </summary>
63 /// <param name="transformFile">Path to the transform file being applied.</param>
64 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param>
65 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions)
66 {
67 var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions);
68 if (0 != error)
69 {
70 throw new MsiException(error);
71 }
72 }
73
74 /// <summary>
75 /// Commits changes made to the database.
76 /// </summary>
77 public void Commit()
78 {
79 // Retry this call 3 times to deal with an MSI internal locking problem.
80 const int retryWait = 300;
81 const int retryLimit = 3;
82 var error = 0;
83
84 for (var i = 1; i <= retryLimit; ++i)
85 {
86 error = MsiInterop.MsiDatabaseCommit(this.Handle);
87
88 if (0 == error)
89 {
90 return;
91 }
92 else
93 {
94 var exception = new MsiException(error);
95
96 // We need to see if the error code is contained in any of the strings in ErrorInfo.
97 // Join the array together and search for the error code to cover the string array.
98 if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString()))
99 {
100 break;
101 }
102
103 Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit));
104 Thread.Sleep(retryWait);
105 }
106 }
107
108 throw new MsiException(error);
109 }
110
111 /// <summary>
112 /// Creates and populates the summary information stream of an existing transform file.
113 /// </summary>
114 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
115 /// <param name="transformFile">The name of the generated transform file.</param>
116 /// <param name="errorConditions">Required error conditions that should be suppressed when the transform is applied.</param>
117 /// <param name="validations">Required when the transform is applied to a database;
118 /// shows which properties should be validated to verify that this transform can be applied to the database.</param>
119 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations)
120 {
121 var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations);
122 if (0 != error)
123 {
124 throw new MsiException(error);
125 }
126 }
127
128 /// <summary>
129 /// Imports an installer text archive table (idt file) into an open database.
130 /// </summary>
131 /// <param name="idtPath">Specifies the path to the file to import.</param>
132 /// <exception cref="WixInvalidIdtException">Attempted to import an IDT file with an invalid format or unsupported data.</exception>
133 /// <exception cref="MsiException">Another error occured while importing the IDT file.</exception>
134 public void Import(string idtPath)
135 {
136 var folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath));
137 var fileName = Path.GetFileName(idtPath);
138
139 var error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName);
140 if (1627 == error) // ERROR_FUNCTION_FAILED
141 {
142 throw new WixInvalidIdtException(idtPath);
143 }
144 else if (0 != error)
145 {
146 throw new MsiException(error);
147 }
148 }
149
150 /// <summary>
151 /// Exports an installer table from an open database to a text archive file (idt file).
152 /// </summary>
153 /// <param name="tableName">Specifies the name of the table to export.</param>
154 /// <param name="folderPath">Specifies the name of the folder that contains archive files. If null or empty string, uses current directory.</param>
155 /// <param name="fileName">Specifies the name of the exported table archive file.</param>
156 public void Export(string tableName, string folderPath, string fileName)
157 {
158 if (String.IsNullOrEmpty(folderPath))
159 {
160 folderPath = Environment.CurrentDirectory;
161 }
162
163 var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName);
164 if (0 != error)
165 {
166 throw new MsiException(error);
167 }
168 }
169
170 /// <summary>
171 /// Creates a transform that, when applied to the reference database, results in this database.
172 /// </summary>
173 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
174 /// <param name="transformFile">The name of the generated transform file. This is optional.</param>
175 /// <returns>true if a transform is generated; false if a transform is not generated because
176 /// there are no differences between the two databases.</returns>
177 public bool GenerateTransform(Database referenceDatabase, string transformFile)
178 {
179 var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0);
180 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found
181 {
182 throw new MsiException(error);
183 }
184
185 return (0xE8 != error);
186 }
187
188 /// <summary>
189 /// Merges two databases together.
190 /// </summary>
191 /// <param name="mergeDatabase">The database to merge into the base database.</param>
192 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
193 /// <returns>True if there were merge conflicts, otherwise false.</returns>
194 public bool Merge(Database mergeDatabase, string tableName)
195 {
196 var error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName);
197 if (error == 1627)
198 {
199 return true;
200 }
201 else if (error != 0)
202 {
203 throw new MsiException(error);
204 }
205
206 return false;
207 }
208
209 /// <summary>
210 /// Prepares a database query and creates a <see cref="View">View</see> object.
211 /// </summary>
212 /// <param name="query">Specifies a SQL query string for querying the database.</param>
213 /// <returns>A view object is returned if the query was successful.</returns>
214 public View OpenView(string query)
215 {
216 return new View(this, query);
217 }
218
219 /// <summary>
220 /// Prepares and executes a database query and creates a <see cref="View">View</see> object.
221 /// </summary>
222 /// <param name="query">Specifies a SQL query string for querying the database.</param>
223 /// <returns>A view object is returned if the query was successful.</returns>
224 public View OpenExecuteView(string query)
225 {
226 var view = new View(this, query);
227
228 view.Execute();
229 return view;
230 }
231
232 /// <summary>
233 /// Verifies the existence or absence of a table.
234 /// </summary>
235 /// <param name="tableName">Table name to to verify the existence of.</param>
236 /// <returns>Returns true if the table exists, false if it does not.</returns>
237 public bool TableExists(string tableName)
238 {
239 var result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName);
240 return MsiInterop.MSICONDITIONTRUE == result;
241 }
242
243 /// <summary>
244 /// Returns a <see cref="Record">Record</see> containing the names of all the primary
245 /// key columns for a specified table.
246 /// </summary>
247 /// <param name="tableName">Specifies the name of the table from which to obtain
248 /// primary key names.</param>
249 /// <returns>Returns a <see cref="Record">Record</see> containing the names of all the
250 /// primary key columns for a specified table.</returns>
251 public Record PrimaryKeys(string tableName)
252 {
253 var error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out var recordHandle);
254 if (error != 0)
255 {
256 throw new MsiException(error);
257 }
258
259 return new Record(recordHandle);
260 }
261 }
262}
diff --git a/src/wix/WixToolset.Core.Native/Msi/InstallLogModes.cs b/src/wix/WixToolset.Core.Native/Msi/InstallLogModes.cs
new file mode 100644
index 00000000..f7012b35
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/InstallLogModes.cs
@@ -0,0 +1,111 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer log modes.
9 /// </summary>
10 [Flags]
11 public enum InstallLogModes
12 {
13 /// <summary>
14 /// Premature termination of installation.
15 /// </summary>
16 FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)),
17
18 /// <summary>
19 /// The error messages are logged.
20 /// </summary>
21 Error = (1 << ((int)InstallMessage.Error >> 24)),
22
23 /// <summary>
24 /// The warning messages are logged.
25 /// </summary>
26 Warning = (1 << ((int)InstallMessage.Warning >> 24)),
27
28 /// <summary>
29 /// The user requests are logged.
30 /// </summary>
31 User = (1 << ((int)InstallMessage.User >> 24)),
32
33 /// <summary>
34 /// The status messages that are not displayed are logged.
35 /// </summary>
36 Info = (1 << ((int)InstallMessage.Info >> 24)),
37
38 /// <summary>
39 /// Request to determine a valid source location.
40 /// </summary>
41 ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)),
42
43 /// <summary>
44 /// The was insufficient disk space.
45 /// </summary>
46 OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)),
47
48 /// <summary>
49 /// The start of new installation actions are logged.
50 /// </summary>
51 ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)),
52
53 /// <summary>
54 /// The data record with the installation action is logged.
55 /// </summary>
56 ActionData = (1 << ((int)InstallMessage.ActionData >> 24)),
57
58 /// <summary>
59 /// The parameters for user-interface initialization are logged.
60 /// </summary>
61 CommonData = (1 << ((int)InstallMessage.CommonData >> 24)),
62
63 /// <summary>
64 /// Logs the property values at termination.
65 /// </summary>
66 PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)),
67
68 /// <summary>
69 /// Sends large amounts of information to a log file not generally useful to users.
70 /// May be used for technical support.
71 /// </summary>
72 Verbose = (1 << ((int)InstallMessage.Initilize >> 24)),
73
74 /// <summary>
75 /// Sends extra debugging information, such as handle creation information, to the log file.
76 /// </summary>
77 ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)),
78
79 /// <summary>
80 /// Progress bar information. This message includes information on units so far and total number of units.
81 /// See MsiProcessMessage for an explanation of the message format.
82 /// This message is only sent to an external user interface and is not logged.
83 /// </summary>
84 Progress = (1 << ((int)InstallMessage.Progress >> 24)),
85
86 /// <summary>
87 /// If this is not a quiet installation, then the basic UI has been initialized.
88 /// If this is a full UI installation, the full UI is not yet initialized.
89 /// This message is only sent to an external user interface and is not logged.
90 /// </summary>
91 Initialize = (1 << ((int)InstallMessage.Initilize >> 24)),
92
93 /// <summary>
94 /// If a full UI is being used, the full UI has ended.
95 /// If this is not a quiet installation, the basic UI has not yet ended.
96 /// This message is only sent to an external user interface and is not logged.
97 /// </summary>
98 Terminate = (1 << ((int)InstallMessage.Terminate >> 24)),
99
100 /// <summary>
101 /// Sent prior to display of the full UI dialog.
102 /// This message is only sent to an external user interface and is not logged.
103 /// </summary>
104 ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)),
105
106 /// <summary>
107 /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed.
108 /// </summary>
109 FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24))
110 }
111}
diff --git a/src/wix/WixToolset.Core.Native/Msi/InstallMessage.cs b/src/wix/WixToolset.Core.Native/Msi/InstallMessage.cs
new file mode 100644
index 00000000..35773e13
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/InstallMessage.cs
@@ -0,0 +1,88 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer message types.
9 /// </summary>
10 [Flags]
11 public enum InstallMessage
12 {
13 /// <summary>
14 /// Premature termination, possibly fatal out of memory.
15 /// </summary>
16 FatalExit = 0x00000000,
17
18 /// <summary>
19 /// Formatted error message, [1] is message number in Error table.
20 /// </summary>
21 Error = 0x01000000,
22
23 /// <summary>
24 /// Formatted warning message, [1] is message number in Error table.
25 /// </summary>
26 Warning = 0x02000000,
27
28 /// <summary>
29 /// User request message, [1] is message number in Error table.
30 /// </summary>
31 User = 0x03000000,
32
33 /// <summary>
34 /// Informative message for log, not to be displayed.
35 /// </summary>
36 Info = 0x04000000,
37
38 /// <summary>
39 /// List of files in use that need to be replaced.
40 /// </summary>
41 FilesInUse = 0x05000000,
42
43 /// <summary>
44 /// Request to determine a valid source location.
45 /// </summary>
46 ResolveSource = 0x06000000,
47
48 /// <summary>
49 /// Insufficient disk space message.
50 /// </summary>
51 OutOfDiskSpace = 0x07000000,
52
53 /// <summary>
54 /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages.
55 /// </summary>
56 ActionStart = 0x08000000,
57
58 /// <summary>
59 /// Action data. Record fields correspond to the template of ACTIONSTART message.
60 /// </summary>
61 ActionData = 0x09000000,
62
63 /// <summary>
64 /// Progress bar information. See the description of record fields below.
65 /// </summary>
66 Progress = 0x0A000000,
67
68 /// <summary>
69 /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0.
70 /// </summary>
71 CommonData = 0x0B000000,
72
73 /// <summary>
74 /// Sent prior to UI initialization, no string data.
75 /// </summary>
76 Initilize = 0x0C000000,
77
78 /// <summary>
79 /// Sent after UI termination, no string data.
80 /// </summary>
81 Terminate = 0x0D000000,
82
83 /// <summary>
84 /// Sent prior to display or authored dialog or wizard.
85 /// </summary>
86 ShowDialog = 0x0E000000
87 }
88}
diff --git a/src/wix/WixToolset.Core.Native/Msi/InstallUILevels.cs b/src/wix/WixToolset.Core.Native/Msi/InstallUILevels.cs
new file mode 100644
index 00000000..e84b5215
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/InstallUILevels.cs
@@ -0,0 +1,72 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer UI levels.
9 /// </summary>
10 [Flags]
11 public enum InstallUILevels
12 {
13 /// <summary>
14 /// No change in the UI level. However, if phWnd is not Null, the parent window can change.
15 /// </summary>
16 NoChange = 0,
17
18 /// <summary>
19 /// The installer chooses an appropriate user interface level.
20 /// </summary>
21 Default = 1,
22
23 /// <summary>
24 /// Completely silent installation.
25 /// </summary>
26 None = 2,
27
28 /// <summary>
29 /// Simple progress and error handling.
30 /// </summary>
31 Basic = 3,
32
33 /// <summary>
34 /// Authored user interface with wizard dialog boxes suppressed.
35 /// </summary>
36 Reduced = 4,
37
38 /// <summary>
39 /// Authored user interface with wizards, progress, and errors.
40 /// </summary>
41 Full = 5,
42
43 /// <summary>
44 /// If combined with the Basic value, the installer shows simple progress dialog boxes but
45 /// does not display a Cancel button on the dialog. This prevents users from canceling the install.
46 /// Available with Windows Installer version 2.0.
47 /// </summary>
48 HideCancel = 0x20,
49
50 /// <summary>
51 /// If combined with the Basic value, the installer shows simple progress
52 /// dialog boxes but does not display any modal dialog boxes or error dialog boxes.
53 /// </summary>
54 ProgressOnly = 0x40,
55
56 /// <summary>
57 /// If combined with any above value, the installer displays a modal dialog
58 /// box at the end of a successful installation or if there has been an error.
59 /// No dialog box is displayed if the user cancels.
60 /// </summary>
61 EndDialog = 0x80,
62
63 /// <summary>
64 /// If this value is combined with the None value, the installer displays only the dialog
65 /// boxes used for source resolution. No other dialog boxes are shown. This value has no
66 /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user
67 /// interface designed to handle all of the UI except for source resolution. In this case,
68 /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later.
69 /// </summary>
70 SourceResOnly = 0x100
71 }
72}
diff --git a/src/wix/WixToolset.Core.Native/Msi/Installer.cs b/src/wix/WixToolset.Core.Native/Msi/Installer.cs
new file mode 100644
index 00000000..8a45aaa8
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/Installer.cs
@@ -0,0 +1,138 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.Diagnostics;
7 using System.Text;
8
9 /// <summary>
10 /// A callback function that the installer calls for progress notification and error messages.
11 /// </summary>
12 /// <param name="context">Pointer to an application context.
13 /// This parameter can be used for error checking.</param>
14 /// <param name="messageType">Specifies a combination of one message box style,
15 /// one message box icon type, one default button, and one installation message type.</param>
16 /// <param name="message">Specifies the message text.</param>
17 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
18 public delegate int InstallUIHandler(IntPtr context, uint messageType, string message);
19
20 /// <summary>
21 /// Represents the Windows Installer, provides wrappers to
22 /// create the top-level objects and access their methods.
23 /// </summary>
24 public static class Installer
25 {
26 /// <summary>
27 /// Extacts the patch metadata as XML.
28 /// </summary>
29 /// <param name="path">Path to patch.</param>
30 /// <returns>String XML.</returns>
31 public static string ExtractPatchXml(string path)
32 {
33 var buffer = new StringBuilder(65535);
34 var size = buffer.Capacity;
35
36 var error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size);
37 if (234 == error)
38 {
39 buffer.EnsureCapacity(++size);
40 error = MsiInterop.MsiExtractPatchXMLData(path, 0, buffer, ref size);
41 }
42
43 if (error != 0)
44 {
45 throw new MsiException(error);
46 }
47
48 return buffer.ToString();
49 }
50
51 /// <summary>
52 /// Takes the path to a file and returns a 128-bit hash of that file.
53 /// </summary>
54 /// <param name="filePath">Path to file that is to be hashed.</param>
55 /// <param name="options">The value in this column must be 0. This parameter is reserved for future use.</param>
56 /// <param name="hash">Int array that receives the returned file hash information.</param>
57 public static void GetFileHash(string filePath, int options, out int[] hash)
58 {
59 var hashInterop = new MSIFILEHASHINFO();
60 hashInterop.FileHashInfoSize = 20;
61
62 var error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop);
63 if (0 != error)
64 {
65 throw new MsiException(error);
66 }
67
68 Debug.Assert(20 == hashInterop.FileHashInfoSize);
69
70 hash = new int[4];
71 hash[0] = hashInterop.Data0;
72 hash[1] = hashInterop.Data1;
73 hash[2] = hashInterop.Data2;
74 hash[3] = hashInterop.Data3;
75 }
76
77 /// <summary>
78 /// Returns the version string and language string in the format that the installer
79 /// expects to find them in the database. If you just want version information, set
80 /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set
81 /// lpVersionBuf and pcchVersionBuf to zero.
82 /// </summary>
83 /// <param name="filePath">Specifies the path to the file.</param>
84 /// <param name="version">Returns the file version. Set to 0 for language information only.</param>
85 /// <param name="language">Returns the file language. Set to 0 for version information only.</param>
86 public static void GetFileVersion(string filePath, out string version, out string language)
87 {
88 var versionLength = 20;
89 var languageLength = 20;
90 var versionBuffer = new StringBuilder(versionLength);
91 var languageBuffer = new StringBuilder(languageLength);
92
93 var error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
94 if (234 == error)
95 {
96 versionBuffer.EnsureCapacity(++versionLength);
97 languageBuffer.EnsureCapacity(++languageLength);
98 error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
99 }
100 else if (1006 == error)
101 {
102 // file has no version or language, so no error
103 error = 0;
104 }
105
106 if (0 != error)
107 {
108 throw new MsiException(error);
109 }
110
111 version = versionBuffer.ToString();
112 language = languageBuffer.ToString();
113 }
114
115 /// <summary>
116 /// Enables an external user-interface handler.
117 /// </summary>
118 /// <param name="installUIHandler">Specifies a callback function.</param>
119 /// <param name="messageFilter">Specifies which messages to handle using the external message handler.</param>
120 /// <param name="context">Pointer to an application context that is passed to the callback function.</param>
121 /// <returns>The return value is the previously set external handler, or null if there was no previously set handler.</returns>
122 public static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context)
123 {
124 return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context);
125 }
126
127 /// <summary>
128 /// Enables the installer's internal user interface.
129 /// </summary>
130 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
131 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.</param>
132 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
133 public static int SetInternalUI(int uiLevel, ref IntPtr hwnd)
134 {
135 return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd);
136 }
137 }
138}
diff --git a/src/wix/WixToolset.Core.Native/Msi/MSIFILEHASHINFO.cs b/src/wix/WixToolset.Core.Native/Msi/MSIFILEHASHINFO.cs
new file mode 100644
index 00000000..ae88ec7e
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/MSIFILEHASHINFO.cs
@@ -0,0 +1,34 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System.Runtime.InteropServices;
6
7 /// <summary>
8 /// contains the file hash information returned by MsiGetFileHash and used in the MsiFileHash table.
9 /// </summary>
10 [StructLayout(LayoutKind.Explicit)]
11 public class MSIFILEHASHINFO
12 {
13 /// <summary>
14 ///
15 /// </summary>
16 [FieldOffset(0)] public uint FileHashInfoSize;
17 /// <summary>
18 ///
19 /// </summary>
20 [FieldOffset(4)] public int Data0;
21 /// <summary>
22 ///
23 /// </summary>
24 [FieldOffset(8)] public int Data1;
25 /// <summary>
26 ///
27 /// </summary>
28 [FieldOffset(12)] public int Data2;
29 /// <summary>
30 ///
31 /// </summary>
32 [FieldOffset(16)] public int Data3;
33 }
34}
diff --git a/src/wix/WixToolset.Core.Native/Msi/ModifyView.cs b/src/wix/WixToolset.Core.Native/Msi/ModifyView.cs
new file mode 100644
index 00000000..989de174
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/ModifyView.cs
@@ -0,0 +1,75 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 /// <summary>
6 /// Enumeration of different modify modes.
7 /// </summary>
8 public enum ModifyView
9 {
10 /// <summary>
11 /// Writes current data in the cursor to a table row. Updates record if the primary
12 /// keys match an existing row and inserts if they do not match. Fails with a read-only
13 /// database. This mode cannot be used with a view containing joins.
14 /// </summary>
15 Assign = 3, // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins.
16
17 /// <summary>
18 /// Remove a row from the table. You must first call the Fetch function with the same
19 /// record. Fails if the row has been deleted. Works only with read-write records. This
20 /// mode cannot be used with a view containing joins.
21 /// </summary>
22 Delete = 6, // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins.
23
24 /// <summary>
25 /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only
26 /// database. This mode cannot be used with a view containing joins.
27 /// </summary>
28 Insert = 1, // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins.
29
30 /// <summary>
31 /// Inserts a temporary record. The information is not persistent. Fails if a row with the
32 /// same primary key exists. Works only with read-write records. This mode cannot be
33 /// used with a view containing joins.
34 /// </summary>
35 InsertTemporary = 7, // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins.
36
37 /// <summary>
38 /// Inserts or validates a record in a table. Inserts if primary keys do not match any row
39 /// and validates if there is a match. Fails if the record does not match the data in
40 /// the table. Fails if there is a record with a duplicate key that is not identical.
41 /// Works only with read-write records. This mode cannot be used with a view containing joins.
42 /// </summary>
43 Merge = 5, // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins.
44
45 /// <summary>
46 /// Refreshes the information in the record. Must first call Fetch with the
47 /// same record. Fails for a deleted row. Works with read-write and read-only records.
48 /// </summary>
49 Refresh = 0, // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records.
50
51 /// <summary>
52 /// Updates or deletes and inserts a record into a table. Must first call Fetch with
53 /// the same record. Updates record if the primary keys are unchanged. Deletes old row and
54 /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot
55 /// be used with a view containing joins.
56 /// </summary>
57 Replace = 4, // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins.
58
59 /// <summary>
60 /// Refreshes the information in the supplied record without changing the position in the
61 /// result set and without affecting subsequent fetch operations. The record may then
62 /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the
63 /// table must be in the query and the record must have at least as many fields as the
64 /// query. Seek cannot be used with multi-table queries. This mode cannot be used with
65 /// a view containing joins. See also the remarks.
66 /// </summary>
67 Seek = -1, // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks.
68
69 /// <summary>
70 /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a
71 /// deleted record. Works only with read-write records.
72 /// </summary>
73 Update = 2, // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records.
74 }
75}
diff --git a/src/wix/WixToolset.Core.Native/Msi/MsiException.cs b/src/wix/WixToolset.Core.Native/Msi/MsiException.cs
new file mode 100644
index 00000000..07c83d81
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/MsiException.cs
@@ -0,0 +1,77 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7
8 /// <summary>
9 /// Exception that wraps MsiGetLastError().
10 /// </summary>
11 [Serializable]
12 public sealed class MsiException : Win32Exception
13 {
14 /// <summary>
15 /// Instantiate a new MsiException with a given error.
16 /// </summary>
17 /// <param name="error">The error code from the MsiXxx() function call.</param>
18 public MsiException(int error) : base(error)
19 {
20 uint handle = MsiInterop.MsiGetLastErrorRecord();
21 if (0 != handle)
22 {
23 using (Record record = new Record(handle))
24 {
25 this.MsiError = record.GetInteger(1);
26
27 int errorInfoCount = record.GetFieldCount() - 1;
28 this.ErrorInfo = new string[errorInfoCount];
29 for (int i = 0; i < errorInfoCount; ++i)
30 {
31 this.ErrorInfo[i] = record.GetString(i + 2);
32 }
33 }
34 }
35 else
36 {
37 this.MsiError = 0;
38 this.ErrorInfo = new string[0];
39 }
40
41 this.Error = error;
42 }
43
44 /// <summary>
45 /// Gets the error number.
46 /// </summary>
47 public int Error { get; private set; }
48
49 /// <summary>
50 /// Gets the internal MSI error number.
51 /// </summary>
52 public int MsiError { get; private set; }
53
54 /// <summary>
55 /// Gets any additional the error information.
56 /// </summary>
57 public string[] ErrorInfo { get; private set; }
58
59 /// <summary>
60 /// Overrides Message property to return useful error message.
61 /// </summary>
62 public override string Message
63 {
64 get
65 {
66 if (0 == this.MsiError)
67 {
68 return base.Message;
69 }
70 else
71 {
72 return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo));
73 }
74 }
75 }
76 }
77}
diff --git a/src/wix/WixToolset.Core.Native/Msi/MsiHandle.cs b/src/wix/WixToolset.Core.Native/Msi/MsiHandle.cs
new file mode 100644
index 00000000..dc2ce605
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/MsiHandle.cs
@@ -0,0 +1,117 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7#if !DEBUG
8 using System.Diagnostics;
9#endif
10 using System.Threading;
11
12 /// <summary>
13 /// Wrapper class for MSI handle.
14 /// </summary>
15 public abstract class MsiHandle : IDisposable
16 {
17 private bool disposed;
18 private uint handle;
19 private int owningThread;
20#if DEBUG
21 private string creationStack;
22#endif
23
24 /// <summary>
25 /// MSI handle destructor.
26 /// </summary>
27 ~MsiHandle()
28 {
29 this.Dispose(false);
30 }
31
32 /// <summary>
33 /// Gets or sets the MSI handle.
34 /// </summary>
35 /// <value>The MSI handle.</value>
36 internal uint Handle
37 {
38 get
39 {
40 if (this.disposed)
41 {
42 throw new ObjectDisposedException("MsiHandle");
43 }
44
45 return this.handle;
46 }
47
48 set
49 {
50 if (this.disposed)
51 {
52 throw new ObjectDisposedException("MsiHandle");
53 }
54
55 this.handle = value;
56 this.owningThread = Thread.CurrentThread.ManagedThreadId;
57#if DEBUG
58 this.creationStack = Environment.StackTrace;
59#endif
60 }
61 }
62
63 /// <summary>
64 /// Close the MSI handle.
65 /// </summary>
66 public void Close()
67 {
68 this.Dispose();
69 }
70
71 /// <summary>
72 /// Disposes the managed and unmanaged objects in this object.
73 /// </summary>
74 public void Dispose()
75 {
76 this.Dispose(true);
77 GC.SuppressFinalize(this);
78 }
79
80 /// <summary>
81 /// Disposes the managed and unmanaged objects in this object.
82 /// </summary>
83 /// <param name="disposing">true to dispose the managed objects.</param>
84 protected virtual void Dispose(bool disposing)
85 {
86 if (!this.disposed)
87 {
88 if (0 != this.handle)
89 {
90 if (Thread.CurrentThread.ManagedThreadId == this.owningThread)
91 {
92 int error = MsiInterop.MsiCloseHandle(this.handle);
93 if (0 != error)
94 {
95 throw new Win32Exception(error);
96 }
97 this.handle = 0;
98 }
99 else
100 {
101 // Don't try to close the handle on a different thread than it was opened.
102 // This will occasionally cause MSI to AV.
103 string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}",
104 this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId);
105#if DEBUG
106 throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack));
107#else
108 Debug.WriteLine(message);
109#endif
110 }
111 }
112
113 this.disposed = true;
114 }
115 }
116 }
117}
diff --git a/src/wix/WixToolset.Core.Native/Msi/MsiInterop.cs b/src/wix/WixToolset.Core.Native/Msi/MsiInterop.cs
new file mode 100644
index 00000000..11ac4094
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/MsiInterop.cs
@@ -0,0 +1,408 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.Text;
7 using System.Runtime.InteropServices;
8
9 /// <summary>
10 /// Class exposing static functions and structs from MSI API.
11 /// </summary>
12 internal static class MsiInterop
13 {
14 // Patching constants
15 internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx
16
17 internal const int MSICONDITIONFALSE = 0; // The table is temporary.
18 internal const int MSICONDITIONTRUE = 1; // The table is persistent.
19 internal const int MSICONDITIONNONE = 2; // The table is unknown.
20 internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function.
21
22 /*
23 internal const int MSIDBOPENREADONLY = 0;
24 internal const int MSIDBOPENTRANSACT = 1;
25 internal const int MSIDBOPENDIRECT = 2;
26 internal const int MSIDBOPENCREATE = 3;
27 internal const int MSIDBOPENCREATEDIRECT = 4;
28 internal const int MSIDBOPENPATCHFILE = 32;
29
30 internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks.
31 internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records.
32 internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins.
33 internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records.
34 internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins.
35 internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins.
36 internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins.
37 internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins.
38 internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins.
39 internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
40 internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
41 internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
42 internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
43
44 internal const uint VTI2 = 2;
45 internal const uint VTI4 = 3;
46 internal const uint VTLPWSTR = 30;
47 internal const uint VTFILETIME = 64;
48 */
49
50 internal const int MSICOLINFONAMES = 0; // return column names
51 internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width
52
53 /// <summary>
54 /// PInvoke of MsiCloseHandle.
55 /// </summary>
56 /// <param name="database">Handle to a database.</param>
57 /// <returns>Error code.</returns>
58 [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)]
59 internal static extern int MsiCloseHandle(uint database);
60
61 /// <summary>
62 /// PInvoke of MsiCreateRecord
63 /// </summary>
64 /// <param name="parameters">Count of columns in the record.</param>
65 /// <returns>Handle referencing the record.</returns>
66 [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
67 internal static extern uint MsiCreateRecord(int parameters);
68
69 /// <summary>
70 /// Creates summary information of an existing transform to include validation and error conditions.
71 /// </summary>
72 /// <param name="database">The handle to the database that contains the new database summary information.</param>
73 /// <param name="referenceDatabase">The handle to the database that contains the original summary information.</param>
74 /// <param name="transformFile">The name of the transform to which the summary information is added.</param>
75 /// <param name="errorConditions">The error conditions that should be suppressed when the transform is applied.</param>
76 /// <param name="validations">Specifies the properties to be validated to verify that the transform can be applied to the database.</param>
77 /// <returns>Error code.</returns>
78 [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)]
79 internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations);
80
81 /// <summary>
82 /// Applies a transform to a database.
83 /// </summary>
84 /// <param name="database">Handle to the database obtained from MsiOpenDatabase to transform.</param>
85 /// <param name="transformFile">Specifies the name of the transform file to apply.</param>
86 /// <param name="errorConditions">Error conditions that should be suppressed.</param>
87 /// <returns>Error code.</returns>
88 [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
89 internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions);
90
91 /// <summary>
92 /// PInvoke of MsiDatabaseCommit.
93 /// </summary>
94 /// <param name="database">Handle to a databse.</param>
95 /// <returns>Error code.</returns>
96 [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)]
97 internal static extern int MsiDatabaseCommit(uint database);
98
99 /// <summary>
100 /// PInvoke of MsiDatabaseExportW.
101 /// </summary>
102 /// <param name="database">Handle to a database.</param>
103 /// <param name="tableName">Table name.</param>
104 /// <param name="folderPath">Folder path.</param>
105 /// <param name="fileName">File name.</param>
106 /// <returns>Error code.</returns>
107 [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
108 internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName);
109
110 /// <summary>
111 /// Generates a transform file of differences between two databases.
112 /// </summary>
113 /// <param name="database">Handle to the database obtained from MsiOpenDatabase that includes the changes.</param>
114 /// <param name="databaseReference">Handle to the database obtained from MsiOpenDatabase that does not include the changes.</param>
115 /// <param name="transformFile">A null-terminated string that specifies the name of the transform file being generated.
116 /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two
117 /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA.
118 /// If the databases are different the function returns NOERROR.</param>
119 /// <param name="reserved1">This is a reserved argument and must be set to 0.</param>
120 /// <param name="reserved2">This is a reserved argument and must be set to 0.</param>
121 /// <returns>Error code.</returns>
122 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
123 internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2);
124
125 /// <summary>
126 /// PInvoke of MsiDatabaseImportW.
127 /// </summary>
128 /// <param name="database">Handle to a database.</param>
129 /// <param name="folderPath">Folder path.</param>
130 /// <param name="fileName">File name.</param>
131 /// <returns>Error code.</returns>
132 [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
133 internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName);
134
135 /// <summary>
136 /// PInvoke of MsiDatabaseMergeW.
137 /// </summary>
138 /// <param name="database">The handle to the database obtained from MsiOpenDatabase.</param>
139 /// <param name="databaseMerge">The handle to the database obtained from MsiOpenDatabase to merge into the base database.</param>
140 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
141 /// <returns>Error code.</returns>
142 [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)]
143 internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName);
144
145 /// <summary>
146 /// PInvoke of MsiDatabaseOpenViewW.
147 /// </summary>
148 /// <param name="database">Handle to a database.</param>
149 /// <param name="query">SQL query.</param>
150 /// <param name="view">View handle.</param>
151 /// <returns>Error code.</returns>
152 [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)]
153 internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view);
154
155 /// <summary>
156 /// PInvoke of MsiExtractPatchXMLDataW.
157 /// </summary>
158 /// <param name="szPatchPath">Path to patch.</param>
159 /// <param name="dwReserved">Reserved for future use.</param>
160 /// <param name="szXMLData">Output XML data.</param>
161 /// <param name="pcchXMLData">Count of characters in XML.</param>
162 /// <returns></returns>
163 [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)]
164 internal static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData);
165
166 /// <summary>
167 /// PInvoke of MsiGetFileHashW.
168 /// </summary>
169 /// <param name="filePath">File path.</param>
170 /// <param name="options">Hash options (must be 0).</param>
171 /// <param name="hash">Buffer to recieve hash.</param>
172 /// <returns>Error code.</returns>
173 [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)]
174 internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash);
175
176 /// <summary>
177 /// PInvoke of MsiGetFileVersionW.
178 /// </summary>
179 /// <param name="filePath">File path.</param>
180 /// <param name="versionBuf">Buffer to receive version info.</param>
181 /// <param name="versionBufSize">Size of version buffer.</param>
182 /// <param name="langBuf">Buffer to recieve lang info.</param>
183 /// <param name="langBufSize">Size of lang buffer.</param>
184 /// <returns>Error code.</returns>
185 [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
186 internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize);
187
188 /// <summary>
189 /// PInvoke of MsiGetLastErrorRecord.
190 /// </summary>
191 /// <returns>Handle to error record if one exists.</returns>
192 [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
193 internal static extern uint MsiGetLastErrorRecord();
194
195 /// <summary>
196 /// PInvoke of MsiDatabaseGetPrimaryKeysW.
197 /// </summary>
198 /// <param name="database">Handle to a database.</param>
199 /// <param name="tableName">Table name.</param>
200 /// <param name="record">Handle to receive resulting record.</param>
201 /// <returns>Error code.</returns>
202 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)]
203 internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record);
204
205 /// <summary>
206 /// PInvoke of MsiDoActionW.
207 /// </summary>
208 /// <param name="product">Handle to the installation provided to a DLL custom action or
209 /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct.</param>
210 /// <param name="action">Specifies the action to execute.</param>
211 /// <returns>Error code.</returns>
212 [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
213 internal static extern int MsiDoAction(uint product, string action);
214
215 /// <summary>
216 /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input.
217 /// </summary>
218 /// <param name="database">Handle to a database.</param>
219 /// <param name="databasePath">Path to a database.</param>
220 /// <param name="updateCount">Max number of updated values.</param>
221 /// <param name="summaryInfo">Handle to summary information.</param>
222 /// <returns>Error code.</returns>
223 [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)]
224 internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo);
225
226 /// <summary>
227 /// PInvoke of MsiDatabaseIsTablePersitentW.
228 /// </summary>
229 /// <param name="database">Handle to a database.</param>
230 /// <param name="tableName">Table name.</param>
231 /// <returns>MSICONDITION</returns>
232 [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)]
233 internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName);
234
235 /// <summary>
236 /// PInvoke of MsiOpenDatabaseW.
237 /// </summary>
238 /// <param name="databasePath">Path to database.</param>
239 /// <param name="persist">Persist mode.</param>
240 /// <param name="database">Handle to database.</param>
241 /// <returns>Error code.</returns>
242 [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)]
243 internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database);
244
245 /// <summary>
246 /// PInvoke of MsiOpenPackageW.
247 /// </summary>
248 /// <param name="packagePath">The path to the package.</param>
249 /// <param name="product">A pointer to a variable that receives the product handle.</param>
250 /// <returns>Error code.</returns>
251 [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)]
252 internal static extern int MsiOpenPackage(string packagePath, out uint product);
253
254 /// <summary>
255 /// PInvoke of MsiRecordIsNull.
256 /// </summary>
257 /// <param name="record">MSI Record handle.</param>
258 /// <param name="field">Index of field to check for null value.</param>
259 /// <returns>true if the field is null, false if not, and an error code for any error.</returns>
260 [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)]
261 internal static extern int MsiRecordIsNull(uint record, int field);
262
263 /// <summary>
264 /// PInvoke of MsiRecordGetInteger.
265 /// </summary>
266 /// <param name="record">MSI Record handle.</param>
267 /// <param name="field">Index of field to retrieve integer from.</param>
268 /// <returns>Integer value.</returns>
269 [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
270 internal static extern int MsiRecordGetInteger(uint record, int field);
271
272 /// <summary>
273 /// PInvoke of MsiRectordSetInteger.
274 /// </summary>
275 /// <param name="record">MSI Record handle.</param>
276 /// <param name="field">Index of field to set integer value in.</param>
277 /// <param name="value">Value to set field to.</param>
278 /// <returns>Error code.</returns>
279 [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
280 internal static extern int MsiRecordSetInteger(uint record, int field, int value);
281
282 /// <summary>
283 /// PInvoke of MsiRecordGetStringW.
284 /// </summary>
285 /// <param name="record">MSI Record handle.</param>
286 /// <param name="field">Index of field to get string value from.</param>
287 /// <param name="valueBuf">Buffer to recieve value.</param>
288 /// <param name="valueBufSize">Size of buffer.</param>
289 /// <returns>Error code.</returns>
290 [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
291 internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize);
292
293 /// <summary>
294 /// PInvoke of MsiRecordSetStringW.
295 /// </summary>
296 /// <param name="record">MSI Record handle.</param>
297 /// <param name="field">Index of field to set string value in.</param>
298 /// <param name="value">String value.</param>
299 /// <returns>Error code.</returns>
300 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
301 internal static extern int MsiRecordSetString(uint record, int field, string value);
302
303 /// <summary>
304 /// PInvoke of MsiRecordSetStreamW.
305 /// </summary>
306 /// <param name="record">MSI Record handle.</param>
307 /// <param name="field">Index of field to set stream value in.</param>
308 /// <param name="filePath">Path to file to set stream value to.</param>
309 /// <returns>Error code.</returns>
310 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)]
311 internal static extern int MsiRecordSetStream(uint record, int field, string filePath);
312
313 /// <summary>
314 /// PInvoke of MsiRecordReadStreamW.
315 /// </summary>
316 /// <param name="record">MSI Record handle.</param>
317 /// <param name="field">Index of field to read stream from.</param>
318 /// <param name="dataBuf">Data buffer to recieve stream value.</param>
319 /// <param name="dataBufSize">Size of data buffer.</param>
320 /// <returns>Error code.</returns>
321 [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)]
322 internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize);
323
324 /// <summary>
325 /// PInvoke of MsiRecordGetFieldCount.
326 /// </summary>
327 /// <param name="record">MSI Record handle.</param>
328 /// <returns>Count of fields in the record.</returns>
329 [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)]
330 internal static extern int MsiRecordGetFieldCount(uint record);
331
332 /// <summary>
333 /// PInvoke of MsiSetExternalUIW.
334 /// </summary>
335 /// <param name="installUIHandler">Specifies a callback function that conforms to the INSTALLUI_HANDLER specification.</param>
336 /// <param name="installLogMode">Specifies which messages to handle using the external message handler. If the external
337 /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged
338 /// if logging has been enabled.</param>
339 /// <param name="context">Pointer to an application context that is passed to the callback function.
340 /// This parameter can be used for error checking.</param>
341 /// <returns>The return value is the previously set external handler, or zero (0) if there was no previously set handler.</returns>
342 [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)]
343 internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context);
344
345 /// <summary>
346 /// PInvoke of MsiSetInternalUI.
347 /// </summary>
348 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
349 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.
350 /// A pointer to the previous owner of the user interface is returned.
351 /// If this parameter is null, the owner of the user interface does not change.</param>
352 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
353 [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)]
354 internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd);
355
356 /// <summary>
357 /// PInvoke of MsiSummaryInfoGetPropertyW.
358 /// </summary>
359 /// <param name="summaryInfo">Handle to summary info.</param>
360 /// <param name="property">Property to get value from.</param>
361 /// <param name="dataType">Data type of property.</param>
362 /// <param name="integerValue">Integer to receive integer value.</param>
363 /// <param name="fileTimeValue">File time to receive file time value.</param>
364 /// <param name="stringValueBuf">String buffer to receive string value.</param>
365 /// <param name="stringValueBufSize">Size of string buffer.</param>
366 /// <returns>Error code.</returns>
367 [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)]
368 internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize);
369
370 /// <summary>
371 /// PInvoke of MsiViewGetColumnInfo.
372 /// </summary>
373 /// <param name="view">Handle to view.</param>
374 /// <param name="columnInfo">Column info.</param>
375 /// <param name="record">Handle for returned record.</param>
376 /// <returns>Error code.</returns>
377 [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)]
378 internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record);
379
380 /// <summary>
381 /// PInvoke of MsiViewExecute.
382 /// </summary>
383 /// <param name="view">Handle of view to execute.</param>
384 /// <param name="record">Handle to a record that supplies the parameters for the view.</param>
385 /// <returns>Error code.</returns>
386 [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)]
387 internal static extern int MsiViewExecute(uint view, uint record);
388
389 /// <summary>
390 /// PInvoke of MsiViewFetch.
391 /// </summary>
392 /// <param name="view">Handle of view to fetch a row from.</param>
393 /// <param name="record">Handle to receive record info.</param>
394 /// <returns>Error code.</returns>
395 [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)]
396 internal static extern int MsiViewFetch(uint view, out uint record);
397
398 /// <summary>
399 /// PInvoke of MsiViewModify.
400 /// </summary>
401 /// <param name="view">Handle of view to modify.</param>
402 /// <param name="modifyMode">Modify mode.</param>
403 /// <param name="record">Handle of record.</param>
404 /// <returns>Error code.</returns>
405 [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)]
406 internal static extern int MsiViewModify(uint view, int modifyMode, uint record);
407 }
408}
diff --git a/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs
new file mode 100644
index 00000000..18a78f77
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/OpenDatabase.cs
@@ -0,0 +1,40 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 /// <summary>
6 /// Enum of predefined persist modes used when opening a database.
7 /// </summary>
8 public enum OpenDatabase
9 {
10 /// <summary>
11 /// Open a database read-only, no persistent changes.
12 /// </summary>
13 ReadOnly = 0,
14
15 /// <summary>
16 /// Open a database read/write in transaction mode.
17 /// </summary>
18 Transact = 1,
19
20 /// <summary>
21 /// Open a database direct read/write without transaction.
22 /// </summary>
23 Direct = 2,
24
25 /// <summary>
26 /// Create a new database, transact mode read/write.
27 /// </summary>
28 Create = 3,
29
30 /// <summary>
31 /// Create a new database, direct mode read/write.
32 /// </summary>
33 CreateDirect = 4,
34
35 /// <summary>
36 /// Indicates a patch file is being opened.
37 /// </summary>
38 OpenPatchFile = 32
39 }
40}
diff --git a/src/wix/WixToolset.Core.Native/Msi/Record.cs b/src/wix/WixToolset.Core.Native/Msi/Record.cs
new file mode 100644
index 00000000..c25e76e2
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/Record.cs
@@ -0,0 +1,181 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Text;
8
9 /// <summary>
10 /// Wrapper class around msi.dll interop for a record.
11 /// </summary>
12 public sealed class Record : MsiHandle
13 {
14 /// <summary>
15 /// Creates a record with the specified number of fields.
16 /// </summary>
17 /// <param name="fieldCount">Number of fields in record.</param>
18 public Record(int fieldCount)
19 {
20 this.Handle = MsiInterop.MsiCreateRecord(fieldCount);
21 if (0 == this.Handle)
22 {
23 throw new OutOfMemoryException();
24 }
25 }
26
27 /// <summary>
28 /// Creates a record from a handle.
29 /// </summary>
30 /// <param name="handle">Handle to create record from.</param>
31 internal Record(uint handle)
32 {
33 this.Handle = handle;
34 }
35
36 /// <summary>
37 /// Gets a string value at specified location.
38 /// </summary>
39 /// <param name="field">Index into record to get string.</param>
40 public string this[int field]
41 {
42 get => this.GetString(field);
43 set => this.SetString(field, value);
44 }
45
46 /// <summary>
47 /// Determines if the value is null at the specified location.
48 /// </summary>
49 /// <param name="field">Index into record of the field to query.</param>
50 /// <returns>true if the value is null, false otherwise.</returns>
51 public bool IsNull(int field)
52 {
53 var error = MsiInterop.MsiRecordIsNull(this.Handle, field);
54
55 switch (error)
56 {
57 case 0:
58 return false;
59 case 1:
60 return true;
61 default:
62 throw new Win32Exception(error);
63 }
64 }
65
66 /// <summary>
67 /// Gets integer value at specified location.
68 /// </summary>
69 /// <param name="field">Index into record to get integer</param>
70 /// <returns>Integer value</returns>
71 public int GetInteger(int field)
72 {
73 return MsiInterop.MsiRecordGetInteger(this.Handle, field);
74 }
75
76 /// <summary>
77 /// Sets integer value at specified location.
78 /// </summary>
79 /// <param name="field">Index into record to set integer.</param>
80 /// <param name="value">Value to set into record.</param>
81 public void SetInteger(int field, int value)
82 {
83 var error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value);
84 if (0 != error)
85 {
86 throw new Win32Exception(error);
87 }
88 }
89
90 /// <summary>
91 /// Gets string value at specified location.
92 /// </summary>
93 /// <param name="field">Index into record to get string.</param>
94 /// <returns>String value</returns>
95 public string GetString(int field)
96 {
97 var bufferSize = 256;
98 var buffer = new StringBuilder(bufferSize);
99 var error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
100 if (234 == error)
101 {
102 buffer.EnsureCapacity(++bufferSize);
103 error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
104 }
105
106 if (0 != error)
107 {
108 throw new Win32Exception(error);
109 }
110
111 return (0 < buffer.Length ? buffer.ToString() : null);
112 }
113
114 /// <summary>
115 /// Set string value at specified location
116 /// </summary>
117 /// <param name="field">Index into record to set string.</param>
118 /// <param name="value">Value to set into record</param>
119 public void SetString(int field, string value)
120 {
121 var error = MsiInterop.MsiRecordSetString(this.Handle, field, value);
122 if (0 != error)
123 {
124 throw new Win32Exception(error);
125 }
126 }
127
128 /// <summary>
129 /// Get stream at specified location.
130 /// </summary>
131 /// <param name="field">Index into record to get stream.</param>
132 /// <param name="buffer">buffer to receive bytes from stream.</param>
133 /// <param name="requestedBufferSize">Buffer size to read.</param>
134 /// <returns>Stream read into string.</returns>
135 public int GetStream(int field, byte[] buffer, int requestedBufferSize)
136 {
137 var bufferSize = buffer.Length;
138 if (requestedBufferSize > 0)
139 {
140 bufferSize = requestedBufferSize;
141 }
142
143 var error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize);
144 if (0 != error)
145 {
146 throw new Win32Exception(error);
147 }
148
149 return bufferSize;
150 }
151
152 /// <summary>
153 /// Sets a stream at a specified location.
154 /// </summary>
155 /// <param name="field">Index into record to set stream.</param>
156 /// <param name="path">Path to file to read into stream.</param>
157 public void SetStream(int field, string path)
158 {
159 var error = MsiInterop.MsiRecordSetStream(this.Handle, field, path);
160 if (0 != error)
161 {
162 throw new Win32Exception(error);
163 }
164 }
165
166 /// <summary>
167 /// Gets the number of fields in record.
168 /// </summary>
169 /// <returns>Count of fields in record.</returns>
170 public int GetFieldCount()
171 {
172 var size = MsiInterop.MsiRecordGetFieldCount(this.Handle);
173 if (0 > size)
174 {
175 throw new Win32Exception();
176 }
177
178 return size;
179 }
180 }
181}
diff --git a/src/wix/WixToolset.Core.Native/Msi/Session.cs b/src/wix/WixToolset.Core.Native/Msi/Session.cs
new file mode 100644
index 00000000..743fb2be
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/Session.cs
@@ -0,0 +1,42 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System.Globalization;
6
7 /// <summary>
8 /// Controls the installation process.
9 /// </summary>
10 public sealed class Session : MsiHandle
11 {
12 /// <summary>
13 /// Instantiate a new Session.
14 /// </summary>
15 /// <param name="database">The database to open.</param>
16 public Session(Database database)
17 {
18 var packagePath = "#" + database.Handle.ToString(CultureInfo.InvariantCulture);
19
20 var error = MsiInterop.MsiOpenPackage(packagePath, out var handle);
21 if (0 != error)
22 {
23 throw new MsiException(error);
24 }
25
26 this.Handle = handle;
27 }
28
29 /// <summary>
30 /// Executes a built-in action, custom action, or user-interface wizard action.
31 /// </summary>
32 /// <param name="action">Specifies the action to execute.</param>
33 public void DoAction(string action)
34 {
35 var error = MsiInterop.MsiDoAction(this.Handle, action);
36 if (0 != error)
37 {
38 throw new MsiException(error);
39 }
40 }
41 }
42}
diff --git a/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs
new file mode 100644
index 00000000..da629df2
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/SummaryInformation.cs
@@ -0,0 +1,376 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.Globalization;
7 using System.Text;
8 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
9
10 /// <summary>
11 /// Summary information for the MSI files.
12 /// </summary>
13 public sealed class SummaryInformation : MsiHandle
14 {
15 /// <summary>
16 /// Summary information properties for products.
17 /// </summary>
18 public enum Package
19 {
20 /// <summary>PID_CODEPAGE = code page of the summary information stream</summary>
21 CodePage = 1,
22
23 /// <summary>PID_TITLE = a brief description of the package type</summary>
24 Title = 2,
25
26 /// <summary>PID_SUBJECT = package name</summary>
27 PackageName = 3,
28
29 /// <summary>PID_AUTHOR = manufacturer of the patch package</summary>
30 Manufacturer = 4,
31
32 /// <summary>PID_KEYWORDS = list of keywords used by file browser</summary>
33 Keywords = 5,
34
35 /// <summary>PID_COMMENTS = general purpose of the package</summary>
36 Comments = 6,
37
38 /// <summary>PID_TEMPLATE = supported platforms and languages</summary>
39 PlatformsAndLanguages = 7,
40
41 /// <summary>PID_LASTAUTHOR should be null for packages</summary>
42 Reserved8 = 8,
43
44 /// <summary>PID_REVNUMBER = GUID package code</summary>
45 PackageCode = 9,
46
47 /// <summary>PID_LASTPRINTED should be null for packages</summary>
48 Reserved11 = 11,
49
50 /// <summary>PID_CREATED datetime when package was created</summary>
51 Created = 12,
52
53 /// <summary>PID_SAVED datetime when package was last modified</summary>
54 LastModified = 13,
55
56 /// <summary>PID_PAGECOUNT minimum required Windows Installer</summary>
57 InstallerRequirement = 14,
58
59 /// <summary>PID_WORDCOUNT elevation privileges of package</summary>
60 FileAndElevatedFlags = 15,
61
62 /// <summary>PID_CHARCOUNT should be null for patches</summary>
63 Reserved16 = 16,
64
65 /// <summary>PID_APPLICATION tool used to create package</summary>
66 BuildTool = 18,
67
68 /// <summary>PID_SECURITY = read-only attribute of the package</summary>
69 Security = 19,
70 }
71
72 /// <summary>
73 /// Summary information properties for transforms.
74 /// </summary>
75 public enum Transform
76 {
77 /// <summary>PID_CODEPAGE = code page for the summary information stream</summary>
78 CodePage = 1,
79
80 /// <summary>PID_TITLE = typically just "Transform"</summary>
81 Title = 2,
82
83 /// <summary>PID_SUBJECT = original subject of target</summary>
84 TargetSubject = 3,
85
86 /// <summary>PID_AUTHOR = original manufacturer of target</summary>
87 TargetManufacturer = 4,
88
89 /// <summary>PID_KEYWORDS = keywords for the transform, typically including at least "Installer"</summary>
90 Keywords = 5,
91
92 /// <summary>PID_COMMENTS = describes what this package does</summary>
93 Comments = 6,
94
95 /// <summary>PID_TEMPLATE = target platform;language</summary>
96 TargetPlatformAndLanguage = 7,
97
98 /// <summary>PID_LASTAUTHOR = updated platform;language</summary>
99 UpdatedPlatformAndLanguage = 8,
100
101 /// <summary>PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode</summary>
102 ProductCodes = 9,
103
104 /// <summary>PID_LASTPRINTED should be null for transforms</summary>
105 Reserved11 = 11,
106
107 ///.<summary>PID_CREATE_DTM = the timestamp when the transform was created</summary>
108 CreationTime = 12,
109
110 /// <summary>PID_PAGECOUNT = minimum installer version</summary>
111 InstallerRequirement = 14,
112
113 /// <summary>PID_CHARCOUNT = validation and error flags</summary>
114 ValidationFlags = 16,
115
116 /// <summary>PID_APPNAME = the application that created the transform</summary>
117 CreatingApplication = 18,
118
119 /// <summary>PID_SECURITY = whether read-only is enforced; should always be 4 for transforms</summary>
120 Security = 19,
121 }
122
123 /// <summary>
124 /// Summary information properties for patches.
125 /// </summary>
126 public enum Patch
127 {
128 /// <summary>PID_CODEPAGE = code page of the summary information stream</summary>
129 CodePage = 1,
130
131 /// <summary>PID_TITLE = a brief description of the package type</summary>
132 Title = 2,
133
134 /// <summary>PID_SUBJECT = package name</summary>
135 PackageName = 3,
136
137 /// <summary>PID_AUTHOR = manufacturer of the patch package</summary>
138 Manufacturer = 4,
139
140 /// <summary>PID_KEYWORDS = alternate sources for the patch package</summary>
141 Sources = 5,
142
143 /// <summary>PID_COMMENTS = general purpose of the patch package</summary>
144 Comments = 6,
145
146 /// <summary>PID_TEMPLATE = semicolon delimited list of ProductCodes</summary>
147 ProductCodes = 7,
148
149 /// <summary>PID_LASTAUTHOR = semicolon delimited list of transform names</summary>
150 TransformNames = 8,
151
152 /// <summary>PID_REVNUMBER = GUID patch code</summary>
153 PatchCode = 9,
154
155 /// <summary>PID_LASTPRINTED should be null for patches</summary>
156 Reserved11 = 11,
157
158 /// <summary>PID_PAGECOUNT should be null for patches</summary>
159 Reserved14 = 14,
160
161 /// <summary>PID_WORDCOUNT = minimum installer version</summary>
162 InstallerRequirement = 15,
163
164 /// <summary>PID_CHARCOUNT should be null for patches</summary>
165 Reserved16 = 16,
166
167 /// <summary>PID_SECURITY = read-only attribute of the patch package</summary>
168 Security = 19,
169 }
170
171 /// <summary>
172 /// Summary information values for the InstallerRequirement property.
173 /// </summary>
174 public enum InstallerRequirement
175 {
176 /// <summary>Any version of the installer will do</summary>
177 Version10 = 1,
178
179 /// <summary>At least 1.2</summary>
180 Version12 = 2,
181
182 /// <summary>At least 2.0</summary>
183 Version20 = 3,
184
185 /// <summary>At least 3.0</summary>
186 Version30 = 4,
187
188 /// <summary>At least 3.1</summary>
189 Version31 = 5,
190 }
191
192 /// <summary>
193 /// Instantiate a new SummaryInformation class from an open database.
194 /// </summary>
195 /// <param name="db">Database to retrieve summary information from.</param>
196 public SummaryInformation(Database db)
197 {
198 if (null == db)
199 {
200 throw new ArgumentNullException(nameof(db));
201 }
202
203 uint handle = 0;
204 var error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle);
205 if (0 != error)
206 {
207 throw new MsiException(error);
208 }
209 this.Handle = handle;
210 }
211
212 /// <summary>
213 /// Instantiate a new SummaryInformation class from a database file.
214 /// </summary>
215 /// <param name="databaseFile">The database file.</param>
216 public SummaryInformation(string databaseFile)
217 {
218 if (null == databaseFile)
219 {
220 throw new ArgumentNullException(nameof(databaseFile));
221 }
222
223 uint handle = 0;
224 var error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle);
225 if (0 != error)
226 {
227 throw new MsiException(error);
228 }
229 this.Handle = handle;
230 }
231
232 /// <summary>
233 /// Gets a summary information package property.
234 /// </summary>
235 /// <param name="property">The summary information package property.</param>
236 /// <returns>The summary information property.</returns>
237 public string GetProperty(Package property)
238 {
239 return this.GetProperty((int)property);
240 }
241
242 /// <summary>
243 /// Gets a summary information package property as a number.
244 /// </summary>
245 /// <param name="property">The summary information package property.</param>
246 /// <returns>The summary information property.</returns>
247 public long GetNumericProperty(Package property)
248 {
249 return this.GetNumericProperty((int)property);
250 }
251
252 /// <summary>
253 /// Gets a summary information patch property.
254 /// </summary>
255 /// <param name="property">The summary information patch property.</param>
256 /// <returns>The summary information property.</returns>
257 public string GetProperty(Patch property)
258 {
259 return this.GetProperty((int)property);
260 }
261
262 /// <summary>
263 /// Gets a summary information transform property.
264 /// </summary>
265 /// <param name="property">The summary information transform property.</param>
266 /// <returns>The summary information property.</returns>
267 public long GetNumericProperty(Transform property)
268 {
269 return this.GetNumericProperty((int)property);
270 }
271
272 /// <summary>
273 /// Gets a summary information property.
274 /// </summary>
275 /// <param name="index">Index of the summary information property.</param>
276 /// <returns>The summary information property.</returns>
277 public string GetProperty(int index)
278 {
279 this.GetSummaryInformationValue(index, out var dataType, out var intValue, out var stringValue, out var timeValue);
280
281 switch ((VT)dataType)
282 {
283 case VT.EMPTY:
284 return String.Empty;
285
286 case VT.LPSTR:
287 return stringValue.ToString();
288
289 case VT.I2:
290 case VT.I4:
291 return Convert.ToString(intValue, CultureInfo.InvariantCulture);
292
293 case VT.FILETIME:
294 var longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime);
295 var dateTime = DateTime.FromFileTime(longFileTime);
296 return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
297
298 default:
299 throw new InvalidOperationException();
300 }
301 }
302
303 /// <summary>
304 /// Gets a summary information property as a number.
305 /// </summary>
306 /// <param name="index">Index of the summary information property.</param>
307 /// <returns>The summary information property.</returns>
308 public long GetNumericProperty(int index)
309 {
310 this.GetSummaryInformationValue(index, out var dataType, out var intValue, out var stringValue, out var timeValue);
311
312 switch ((VT)dataType)
313 {
314 case VT.EMPTY:
315 return 0;
316
317 case VT.LPSTR:
318 return Int64.Parse(stringValue.ToString(), CultureInfo.InvariantCulture);
319
320 case VT.I2:
321 case VT.I4:
322 return intValue;
323
324 case VT.FILETIME:
325 return (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime);
326
327 default:
328 throw new InvalidOperationException();
329 }
330 }
331
332 private void GetSummaryInformationValue(int index, out uint dataType, out int intValue, out StringBuilder stringValue, out FILETIME timeValue)
333 {
334 var bufSize = 64;
335 stringValue = new StringBuilder(bufSize);
336 timeValue.dwHighDateTime = 0;
337 timeValue.dwLowDateTime = 0;
338
339 var error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize);
340 if (234 == error)
341 {
342 stringValue.EnsureCapacity(++bufSize);
343 error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize);
344 }
345
346 if (0 != error)
347 {
348 throw new MsiException(error);
349 }
350 }
351
352 /// <summary>
353 /// Variant types in the summary information table.
354 /// </summary>
355 private enum VT : uint
356 {
357 /// <summary>Variant has not been assigned.</summary>
358 EMPTY = 0,
359
360 /// <summary>Null variant type.</summary>
361 NULL = 1,
362
363 /// <summary>16-bit integer variant type.</summary>
364 I2 = 2,
365
366 /// <summary>32-bit integer variant type.</summary>
367 I4 = 3,
368
369 /// <summary>String variant type.</summary>
370 LPSTR = 30,
371
372 /// <summary>Date time (FILETIME, converted to Variant time) variant type.</summary>
373 FILETIME = 64,
374 }
375 }
376}
diff --git a/src/wix/WixToolset.Core.Native/Msi/TransformErrorConditions.cs b/src/wix/WixToolset.Core.Native/Msi/TransformErrorConditions.cs
new file mode 100644
index 00000000..313dceeb
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/TransformErrorConditions.cs
@@ -0,0 +1,58 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// The errors to suppress when applying a transform.
9 /// </summary>
10 [Flags]
11 public enum TransformErrorConditions
12 {
13 /// <summary>
14 /// None of the following conditions.
15 /// </summary>
16 None = 0x0,
17
18 /// <summary>
19 /// Suppress error when adding a row that exists.
20 /// </summary>
21 AddExistingRow = 0x1,
22
23 /// <summary>
24 /// Suppress error when deleting a row that does not exist.
25 /// </summary>
26 DeleteMissingRow = 0x2,
27
28 /// <summary>
29 /// Suppress error when adding a table that exists.
30 /// </summary>
31 AddExistingTable = 0x4,
32
33 /// <summary>
34 /// Suppress error when deleting a table that does not exist.
35 /// </summary>
36 DeleteMissingTable = 0x8,
37
38 /// <summary>
39 /// Suppress error when updating a row that does not exist.
40 /// </summary>
41 UpdateMissingRow = 0x10,
42
43 /// <summary>
44 /// Suppress error when transform and database code pages do not match, and their code pages are neutral.
45 /// </summary>
46 ChangeCodepage = 0x20,
47
48 /// <summary>
49 /// Create the temporary _TransformView table when applying a transform.
50 /// </summary>
51 ViewTransform = 0x100,
52
53 /// <summary>
54 /// Suppress all errors but the option to create the temporary _TransformView table.
55 /// </summary>
56 All = 0x3F
57 }
58}
diff --git a/src/wix/WixToolset.Core.Native/Msi/TransformValidations.cs b/src/wix/WixToolset.Core.Native/Msi/TransformValidations.cs
new file mode 100644
index 00000000..52bddeaf
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/TransformValidations.cs
@@ -0,0 +1,73 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// The validation to run while applying a transform.
9 /// </summary>
10 [Flags]
11 public enum TransformValidations
12 {
13 /// <summary>
14 /// Do not validate properties.
15 /// </summary>
16 None = 0x0,
17
18 /// <summary>
19 /// Default language must match base database.
20 /// </summary>
21 Language = 0x1,
22
23 /// <summary>
24 /// Product must match base database.
25 /// </summary>
26 Product = 0x2,
27
28 /// <summary>
29 /// Check major version only.
30 /// </summary>
31 MajorVersion = 0x8,
32
33 /// <summary>
34 /// Check major and minor versions only.
35 /// </summary>
36 MinorVersion = 0x10,
37
38 /// <summary>
39 /// Check major, minor, and update versions.
40 /// </summary>
41 UpdateVersion = 0x20,
42
43 /// <summary>
44 /// Installed version &lt; base version.
45 /// </summary>
46 NewLessBaseVersion = 0x40,
47
48 /// <summary>
49 /// Installed version &lt;= base version.
50 /// </summary>
51 NewLessEqualBaseVersion = 0x80,
52
53 /// <summary>
54 /// Installed version = base version.
55 /// </summary>
56 NewEqualBaseVersion = 0x100,
57
58 /// <summary>
59 /// Installed version &gt;= base version.
60 /// </summary>
61 NewGreaterEqualBaseVersion = 0x200,
62
63 /// <summary>
64 /// Installed version &gt; base version.
65 /// </summary>
66 NewGreaterBaseVersion = 0x400,
67
68 /// <summary>
69 /// UpgradeCode must match base database.
70 /// </summary>
71 UpgradeCode = 0x800
72 }
73}
diff --git a/src/wix/WixToolset.Core.Native/Msi/View.cs b/src/wix/WixToolset.Core.Native/Msi/View.cs
new file mode 100644
index 00000000..6305a9de
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/View.cs
@@ -0,0 +1,206 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9
10 /// <summary>
11 /// Wrapper class for MSI API views.
12 /// </summary>
13 public sealed class View : MsiHandle
14 {
15 /// <summary>
16 /// Constructor that creates a view given a database handle and a query.
17 /// </summary>
18 /// <param name="db">Handle to the database to run the query on.</param>
19 /// <param name="query">Query to be executed.</param>
20 public View(Database db, string query)
21 {
22 if (null == db)
23 {
24 throw new ArgumentNullException(nameof(db));
25 }
26
27 if (null == query)
28 {
29 throw new ArgumentNullException(nameof(query));
30 }
31
32 var error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out var handle);
33 if (0 != error)
34 {
35 throw new MsiException(error);
36 }
37
38 this.Handle = handle;
39 }
40
41 /// <summary>
42 /// Enumerator that automatically disposes of the retrieved Records.
43 /// </summary>
44 public IEnumerable<Record> Records => new ViewEnumerable(this);
45
46 /// <summary>
47 /// Executes a view with no customizable parameters.
48 /// </summary>
49 public void Execute()
50 {
51 this.Execute(null);
52 }
53
54 /// <summary>
55 /// Executes a query substituing the values from the records into the customizable parameters
56 /// in the view.
57 /// </summary>
58 /// <param name="record">Record containing parameters to be substituded into the view.</param>
59 public void Execute(Record record)
60 {
61 var error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle);
62 if (0 != error)
63 {
64 throw new MsiException(error);
65 }
66 }
67
68 /// <summary>
69 /// Fetches the next row in the view.
70 /// </summary>
71 /// <returns>Returns the fetched record; otherwise null.</returns>
72 public Record Fetch()
73 {
74 var error = MsiInterop.MsiViewFetch(this.Handle, out var recordHandle);
75 if (259 == error)
76 {
77 return null;
78 }
79 else if (0 != error)
80 {
81 throw new MsiException(error);
82 }
83
84 return new Record(recordHandle);
85 }
86
87 /// <summary>
88 /// Updates a fetched record.
89 /// </summary>
90 /// <param name="type">Type of modification mode.</param>
91 /// <param name="record">Record to be modified.</param>
92 public void Modify(ModifyView type, Record record)
93 {
94 var error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle);
95 if (0 != error)
96 {
97 throw new MsiException(error);
98 }
99 }
100
101 /// <summary>
102 /// Get the column names in a record.
103 /// </summary>
104 /// <returns></returns>
105 public Record GetColumnNames()
106 {
107 return this.GetColumnInfo(MsiInterop.MSICOLINFONAMES);
108 }
109
110 /// <summary>
111 /// Get the column types in a record.
112 /// </summary>
113 /// <returns></returns>
114 public Record GetColumnTypes()
115 {
116 return this.GetColumnInfo(MsiInterop.MSICOLINFOTYPES);
117 }
118
119 /// <summary>
120 /// Returns a record containing column names or definitions.
121 /// </summary>
122 /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param>
123 /// <returns>The record containing information about the column.</returns>
124 public Record GetColumnInfo(int columnType)
125 {
126
127 var error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out var recordHandle);
128 if (0 != error)
129 {
130 throw new MsiException(error);
131 }
132
133 return new Record(recordHandle);
134 }
135
136 private class ViewEnumerable : IEnumerable<Record>
137 {
138 private readonly View view;
139
140 public ViewEnumerable(View view) => this.view = view;
141
142 public IEnumerator<Record> GetEnumerator() => new ViewEnumerator(this.view);
143
144 IEnumerator IEnumerable.GetEnumerator() => new ViewEnumerator(this.view);
145 }
146
147 private class ViewEnumerator : IEnumerator<Record>
148 {
149 private readonly View view;
150 private readonly List<Record> records = new List<Record>();
151 private int position = -1;
152 private bool disposed;
153
154 public ViewEnumerator(View view) => this.view = view;
155
156 public Record Current => this.records[this.position];
157
158 object IEnumerator.Current => this.records[this.position];
159
160 public bool MoveNext()
161 {
162 if (this.position + 1 >= this.records.Count)
163 {
164 var record = this.view.Fetch();
165
166 if (record == null)
167 {
168 return false;
169 }
170
171 this.records.Add(record);
172 this.position = this.records.Count - 1;
173 }
174 else
175 {
176 ++this.position;
177 }
178
179 return true;
180 }
181
182 public void Reset() => this.position = -1;
183
184 public void Dispose()
185 {
186 this.Dispose(true);
187 }
188
189 protected virtual void Dispose(bool disposing)
190 {
191 if (!this.disposed)
192 {
193 if (disposing)
194 {
195 foreach (var record in this.records)
196 {
197 record.Dispose();
198 }
199 }
200
201 this.disposed = true;
202 }
203 }
204 }
205 }
206}
diff --git a/src/wix/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs b/src/wix/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs
new file mode 100644
index 00000000..268ddc11
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs
@@ -0,0 +1,49 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msi
4{
5 using System;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// WiX invalid idt exception.
10 /// </summary>
11 [Serializable]
12 public sealed class WixInvalidIdtException : WixException
13 {
14 /// <summary>
15 /// Instantiate a new WixInvalidIdtException.
16 /// </summary>
17 public WixInvalidIdtException()
18 {
19 }
20
21 /// <summary>
22 /// Instantiate a new WixInvalidIdtException.
23 /// </summary>
24 /// <param name="message"></param>
25 /// <param name="innerException"></param>
26 public WixInvalidIdtException(string message, Exception innerException) : base(message, innerException)
27 {
28 }
29
30 /// <summary>
31 /// Instantiate a new WixInvalidIdtException.
32 /// </summary>
33 /// <param name="idtFile">The invalid idt file.</param>
34 public WixInvalidIdtException(string idtFile) :
35 base(ErrorMessages.InvalidIdt(new SourceLineNumber(idtFile), idtFile))
36 {
37 }
38
39 /// <summary>
40 /// Instantiate a new WixInvalidIdtException.
41 /// </summary>
42 /// <param name="idtFile">The invalid idt file.</param>
43 /// <param name="tableName">The table name of the invalid idt file.</param>
44 public WixInvalidIdtException(string idtFile, string tableName) :
45 base(ErrorMessages.InvalidIdt(new SourceLineNumber(idtFile), idtFile, tableName))
46 {
47 }
48 }
49}
diff --git a/src/wix/WixToolset.Core.Native/Msm/ConfigurationCallback.cs b/src/wix/WixToolset.Core.Native/Msm/ConfigurationCallback.cs
new file mode 100644
index 00000000..31b06d02
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/ConfigurationCallback.cs
@@ -0,0 +1,90 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8
9 /// <summary>
10 /// Callback object for configurable merge modules.
11 /// </summary>
12 public sealed class ConfigurationCallback : IMsmConfigureModule
13 {
14 private const int SOk = 0x0;
15 private const int SFalse = 0x1;
16 private readonly Hashtable configurationData;
17
18 /// <summary>
19 /// Creates a ConfigurationCallback object.
20 /// </summary>
21 /// <param name="configData">String to break up into name/value pairs.</param>
22 public ConfigurationCallback(string configData)
23 {
24 if (String.IsNullOrEmpty(configData))
25 {
26 throw new ArgumentNullException(nameof(configData));
27 }
28
29 var pairs = configData.Split(',');
30 this.configurationData = new Hashtable(pairs.Length);
31 for (var i = 0; i < pairs.Length; ++i)
32 {
33 var nameVal = pairs[i].Split('=');
34 var name = nameVal[0];
35 var value = nameVal[1];
36
37 name = name.Replace("%2C", ",");
38 name = name.Replace("%3D", "=");
39 name = name.Replace("%25", "%");
40
41 value = value.Replace("%2C", ",");
42 value = value.Replace("%3D", "=");
43 value = value.Replace("%25", "%");
44
45 this.configurationData[name] = value;
46 }
47 }
48
49 /// <summary>
50 /// Returns text data based on name.
51 /// </summary>
52 /// <param name="name">Name of value to return.</param>
53 /// <param name="configData">Out param to put configuration data into.</param>
54 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
55 public int ProvideTextData(string name, out string configData)
56 {
57 if (this.configurationData.Contains(name))
58 {
59 configData = (string)this.configurationData[name];
60 return SOk;
61 }
62 else
63 {
64 configData = null;
65 return SFalse;
66 }
67 }
68
69 /// <summary>
70 /// Returns integer data based on name.
71 /// </summary>
72 /// <param name="name">Name of value to return.</param>
73 /// <param name="configData">Out param to put configuration data into.</param>
74 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
75 public int ProvideIntegerData(string name, out int configData)
76 {
77 if (this.configurationData.Contains(name))
78 {
79 var val = (string)this.configurationData[name];
80 configData = Convert.ToInt32(val, CultureInfo.InvariantCulture);
81 return SOk;
82 }
83 else
84 {
85 configData = 0;
86 return SFalse;
87 }
88 }
89 }
90}
diff --git a/src/wix/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs b/src/wix/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs
new file mode 100644
index 00000000..468fb1fc
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs
@@ -0,0 +1,32 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Callback for configurable merge modules.
10 /// </summary>
11 [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
12 public interface IMsmConfigureModule
13 {
14 /// <summary>
15 /// Callback to retrieve text data for configurable merge modules.
16 /// </summary>
17 /// <param name="name">Name of the data to be retrieved.</param>
18 /// <param name="configData">The data corresponding to the name.</param>
19 /// <returns>The error code (HRESULT).</returns>
20 [PreserveSig]
21 int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData);
22
23 /// <summary>
24 /// Callback to retrieve integer data for configurable merge modules.
25 /// </summary>
26 /// <param name="name">Name of the data to be retrieved.</param>
27 /// <param name="configData">The data corresponding to the name.</param>
28 /// <returns>The error code (HRESULT).</returns>
29 [PreserveSig]
30 int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData);
31 }
32}
diff --git a/src/wix/WixToolset.Core.Native/Msm/IMsmError.cs b/src/wix/WixToolset.Core.Native/Msm/IMsmError.cs
new file mode 100644
index 00000000..4f1325a6
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/IMsmError.cs
@@ -0,0 +1,77 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// A merge error.
10 /// </summary>
11 [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")]
12 public interface IMsmError
13 {
14 /// <summary>
15 /// Gets the type of merge error.
16 /// </summary>
17 /// <value>The type of merge error.</value>
18 MsmErrorType Type
19 {
20 get;
21 }
22
23 /// <summary>
24 /// Gets the path information from the merge error.
25 /// </summary>
26 /// <value>The path information from the merge error.</value>
27 string Path
28 {
29 get;
30 }
31
32 /// <summary>
33 /// Gets the language information from the merge error.
34 /// </summary>
35 /// <value>The language information from the merge error.</value>
36 short Language
37 {
38 get;
39 }
40
41 /// <summary>
42 /// Gets the database table from the merge error.
43 /// </summary>
44 /// <value>The database table from the merge error.</value>
45 string DatabaseTable
46 {
47 get;
48 }
49
50 /// <summary>
51 /// Gets the collection of database keys from the merge error.
52 /// </summary>
53 /// <value>The collection of database keys from the merge error.</value>
54 IMsmStrings DatabaseKeys
55 {
56 get;
57 }
58
59 /// <summary>
60 /// Gets the module table from the merge error.
61 /// </summary>
62 /// <value>The module table from the merge error.</value>
63 string ModuleTable
64 {
65 get;
66 }
67
68 /// <summary>
69 /// Gets the collection of module keys from the merge error.
70 /// </summary>
71 /// <value>The collection of module keys from the merge error.</value>
72 IMsmStrings ModuleKeys
73 {
74 get;
75 }
76 }
77}
diff --git a/src/wix/WixToolset.Core.Native/Msm/IMsmErrors.cs b/src/wix/WixToolset.Core.Native/Msm/IMsmErrors.cs
new file mode 100644
index 00000000..e1472376
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/IMsmErrors.cs
@@ -0,0 +1,32 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Collection of merge errors.
10 /// </summary>
11 [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")]
12 public interface IMsmErrors
13 {
14 /// <summary>
15 /// Gets the IMsmError at the specified index.
16 /// </summary>
17 /// <param name="index">The one-based index of the IMsmError to get.</param>
18 IMsmError this[int index]
19 {
20 get;
21 }
22
23 /// <summary>
24 /// Gets the count of IMsmErrors in this collection.
25 /// </summary>
26 /// <value>The count of IMsmErrors in this collection.</value>
27 int Count
28 {
29 get;
30 }
31 }
32}
diff --git a/src/wix/WixToolset.Core.Native/Msm/IMsmMerge2.cs b/src/wix/WixToolset.Core.Native/Msm/IMsmMerge2.cs
new file mode 100644
index 00000000..400249e7
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/IMsmMerge2.cs
@@ -0,0 +1,174 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// IMsmMerge2 interface.
10 /// </summary>
11 [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")]
12 public interface IMsmMerge2
13 {
14 /// <summary>
15 /// The OpenDatabase method of the Merge object opens a Windows Installer installation
16 /// database, located at a specified path, that is to be merged with a module.
17 /// </summary>
18 /// <param name="path">Path to the database being opened.</param>
19 void OpenDatabase(string path);
20
21 /// <summary>
22 /// The OpenModule method of the Merge object opens a Windows Installer merge module
23 /// in read-only mode. A module must be opened before it can be merged with an installation database.
24 /// </summary>
25 /// <param name="fileName">Fully qualified file name pointing to a merge module.</param>
26 /// <param name="language">A valid language identifier (LANGID).</param>
27 void OpenModule(string fileName, short language);
28
29 /// <summary>
30 /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database.
31 /// </summary>
32 /// <param name="commit">true if changes should be saved, false otherwise.</param>
33 void CloseDatabase(bool commit);
34
35 /// <summary>
36 /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module.
37 /// </summary>
38 void CloseModule();
39
40 /// <summary>
41 /// The OpenLog method of the Merge object opens a log file that receives progress and error messages.
42 /// If the log file already exists, the installer appends new messages. If the log file does not exist,
43 /// the installer creates a log file.
44 /// </summary>
45 /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param>
46 void OpenLog(string fileName);
47
48 /// <summary>
49 /// The CloseLog method of the Merge object closes the current log file.
50 /// </summary>
51 void CloseLog();
52
53 /// <summary>
54 /// The Log method of the Merge object writes a text string to the currently open log file.
55 /// </summary>
56 /// <param name="message">The text string to display.</param>
57 void Log(string message);
58
59 /// <summary>
60 /// Gets the errors from the last merge operation.
61 /// </summary>
62 /// <value>The errors from the last merge operation.</value>
63 IMsmErrors Errors
64 {
65 get;
66 }
67
68 /// <summary>
69 /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.
70 /// </summary>
71 /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value>
72 object Dependencies
73 {
74 get;
75 }
76
77 /// <summary>
78 /// The Merge method of the Merge object executes a merge of the current database and current
79 /// module. The merge attaches the components in the module to the feature identified by Feature.
80 /// The root of the module's directory tree is redirected to the location given by RedirectDir.
81 /// </summary>
82 /// <param name="feature">The name of a feature in the database.</param>
83 /// <param name="redirectDir">The key of an entry in the Directory table of the database.
84 /// This parameter may be NULL or an empty string.</param>
85 void Merge(string feature, string redirectDir);
86
87 /// <summary>
88 /// The Connect method of the Merge object connects a module to an additional feature.
89 /// The module must have already been merged into the database or will be merged into the database.
90 /// The feature must exist before calling this function.
91 /// </summary>
92 /// <param name="feature">The name of a feature already existing in the database.</param>
93 void Connect(string feature);
94
95 /// <summary>
96 /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and
97 /// saves it as the specified file. The installer creates this file if it does not already exist
98 /// and overwritten if it does exist.
99 /// </summary>
100 /// <param name="fileName">The fully qualified destination file.</param>
101 void ExtractCAB(string fileName);
102
103 /// <summary>
104 /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module
105 /// and then writes those files to the destination directory.
106 /// </summary>
107 /// <param name="path">The fully qualified destination directory.</param>
108 void ExtractFiles(string path);
109
110 /// <summary>
111 /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it
112 /// takes an extra argument. The Merge method executes a merge of the current database and
113 /// current module. The merge attaches the components in the module to the feature identified
114 /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir.
115 /// </summary>
116 /// <param name="feature">The name of a feature in the database.</param>
117 /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may
118 /// be NULL or an empty string.</param>
119 /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may
120 /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration
121 /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param>
122 void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration);
123
124 /// <summary>
125 /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and
126 /// then writes those files to the destination directory.
127 /// </summary>
128 /// <param name="path">The fully qualified destination directory.</param>
129 /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param>
130 /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
131 /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
132 void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths);
133
134 /// <summary>
135 /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.
136 /// </summary>
137 /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value>
138 /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer.
139 /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum().
140 /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks>
141 object ConfigurableItems
142 {
143 get;
144 }
145
146 /// <summary>
147 /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to
148 /// a source image on disk after a merge, taking into account changes to the module that might have been made
149 /// during module configuration. The list of files to be extracted is taken from the file table of the module
150 /// during the merge process. The list of files consists of every file successfully copied from the file table
151 /// of the module to the target database. File table entries that were not copied due to primary key conflicts
152 /// with existing rows in the database are not a part of this list. At image creation time, the directory for
153 /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is
154 /// the root of the source image for the install. fLongFileNames determines whether or not long file names are
155 /// used for both path segments and final file names. The function fails if no database is open, no module is
156 /// open, or no merge has been performed.
157 /// </summary>
158 /// <param name="path">The path of the root of the source image for the install.</param>
159 /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param>
160 /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
161 /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
162 void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths);
163
164 /// <summary>
165 /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function
166 /// returns the primary keys in the File table of the currently open module. The primary keys are returned
167 /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles.
168 /// </summary>
169 IMsmStrings ModuleFiles
170 {
171 get;
172 }
173 }
174}
diff --git a/src/wix/WixToolset.Core.Native/Msm/IMsmStrings.cs b/src/wix/WixToolset.Core.Native/Msm/IMsmStrings.cs
new file mode 100644
index 00000000..41063bfa
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/IMsmStrings.cs
@@ -0,0 +1,32 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// A collection of strings.
10 /// </summary>
11 [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")]
12 public interface IMsmStrings
13 {
14 /// <summary>
15 /// Gets the string at the specified index.
16 /// </summary>
17 /// <param name="index">The one-based index of the string to get.</param>
18 string this[int index]
19 {
20 get;
21 }
22
23 /// <summary>
24 /// Gets the count of strings in this collection.
25 /// </summary>
26 /// <value>The count of strings in this collection.</value>
27 int Count
28 {
29 get;
30 }
31 }
32}
diff --git a/src/wix/WixToolset.Core.Native/Msm/MsmErrorType.cs b/src/wix/WixToolset.Core.Native/Msm/MsmErrorType.cs
new file mode 100644
index 00000000..c67d37b4
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/MsmErrorType.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Errors returned by merge operations.
10 /// </summary>
11 [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")]
12 public enum MsmErrorType
13 {
14 /// <summary>
15 /// A request was made to open a module with a language not supported by the module.
16 /// No more general language is supported by the module.
17 /// Adds msmErrorLanguageUnsupported to the Type property and the requested language
18 /// to the Language Property (Error Object). All Error object properties are empty.
19 /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
20 /// </summary>
21 msmErrorLanguageUnsupported = 1,
22
23 /// <summary>
24 /// A request was made to open a module with a supported language but the module has
25 /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property
26 /// and the applied transform's language to the Language Property of the Error object.
27 /// This may not be the requested language if a more general language was used.
28 /// All other properties of the Error object are empty. The OpenModule function
29 /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
30 /// </summary>
31 msmErrorLanguageFailed = 2,
32
33 /// <summary>
34 /// The module cannot be merged because it excludes, or is excluded by, another module
35 /// in the database. Adds msmErrorExclusion to the Type property of the Error object.
36 /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the
37 /// excluded module's row in the ModuleExclusion table. If an existing module excludes
38 /// the module being merged, the excluded module's ModuleSignature information is added
39 /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys
40 /// contains the excluded module's ModuleSignature information. All other properties
41 /// are empty (or -1).
42 /// </summary>
43 msmErrorExclusion = 3,
44
45 /// <summary>
46 /// Merge conflict during merge. The value of the Type property is set to
47 /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain
48 /// the table name and primary keys of the conflicting row in the database. The
49 /// ModuleTable property and ModuleKeys property contain the table name and primary keys
50 /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be
51 /// null if the row does not exist in the database. For example, if the conflict is in a
52 /// generated FeatureComponents table entry. On Windows Installer version 2.0, when
53 /// merging a configurable merge module, configuration may cause these properties to
54 /// refer to rows that do not exist in the module.
55 /// </summary>
56 msmErrorTableMerge = 4,
57
58 /// <summary>
59 /// There was a problem resequencing a sequence table to contain the necessary merged
60 /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable
61 /// and DatabaseKeys properties contain the sequence table name and primary keys
62 /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties
63 /// contain the sequence table name and primary key (action name) of the conflicting row.
64 /// On Windows Installer version 2.0, when merging a configurable merge module,
65 /// configuration may cause these properties to refer to rows that do not exist in the module.
66 /// </summary>
67 msmErrorResequenceMerge = 5,
68
69 /// <summary>
70 /// Not used.
71 /// </summary>
72 msmErrorFileCreate = 6,
73
74 /// <summary>
75 /// There was a problem creating a directory to extract a file to disk. The Path property
76 /// contains the directory that could not be created. All other properties are empty or -1.
77 /// Not available with Windows Installer version 1.0.
78 /// </summary>
79 msmErrorDirCreate = 7,
80
81 /// <summary>
82 /// A feature name is required to complete the merge, but no feature name was provided.
83 /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys
84 /// contain the table name and primary keys of the conflicting row. The ModuleTable and
85 /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged.
86 /// On Windows Installer version 2.0, when merging a configurable merge module, configuration
87 /// may cause these properties to refer to rows that do not exist in the module.
88 /// If the failure is in a generated FeatureComponents table, the DatabaseTable and
89 /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to
90 /// the row in the Component table causing the failure.
91 /// </summary>
92 msmErrorFeatureRequired = 8,
93
94 /// <summary>
95 /// Available with Window Installer version 2.0. Substitution of a Null value into a
96 /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and
97 /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row
98 /// into the ModuleTable property and ModuleKeys property. All other properties of the Error
99 /// object are set to an empty string or -1. This error causes the immediate failure of the
100 /// merge and the MergeEx function to return E_FAIL.
101 /// </summary>
102 msmErrorBadNullSubstitution = 9,
103
104 /// <summary>
105 /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer
106 /// Format Type into a Binary Type data column. This type of error returns
107 /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the
108 /// keys from the ModuleSubstitution table for this row into the ModuleTable property.
109 /// All other properties of the Error object are set to an empty string or -1. This error
110 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
111 /// </summary>
112 msmErrorBadSubstitutionType = 10,
113
114 /// <summary>
115 /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table
116 /// references a configuration item not defined in the ModuleConfiguration table.
117 /// This type of error returns msmErrorMissingConfigItem in the Type property and enters
118 /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into
119 /// the ModuleTable property. All other properties of the Error object are set to an empty
120 /// string or -1. This error causes the immediate failure of the merge and the MergeEx
121 /// function to return E_FAIL.
122 /// </summary>
123 msmErrorMissingConfigItem = 11,
124
125 /// <summary>
126 /// Available with Window Installer version 2.0. The authoring tool has returned a Null
127 /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this
128 /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution"
129 /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property.
130 /// All other properties of the Error object are set to an empty string or -1. This error
131 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
132 /// </summary>
133 msmErrorBadNullResponse = 12,
134
135 /// <summary>
136 /// Available with Window Installer version 2.0. The authoring tool returned a failure code
137 /// (not S_OK or S_FALSE) when asked for data. An error of this type will return
138 /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution"
139 /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property.
140 /// All other properties of the Error object are set to an empty string or -1. This error
141 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
142 /// </summary>
143 msmErrorDataRequestFailed = 13,
144
145 /// <summary>
146 /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was
147 /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of
148 /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of
149 /// the error object are set to an empty string or -1. This error causes the immediate failure
150 /// of the merge and causes the Merge function or MergeEx function to return E_FAIL.
151 /// </summary>
152 msmErrorPlatformMismatch = 14,
153 }
154}
diff --git a/src/wix/WixToolset.Core.Native/Msm/MsmInterop.cs b/src/wix/WixToolset.Core.Native/Msm/MsmInterop.cs
new file mode 100644
index 00000000..d2627904
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Msm/MsmInterop.cs
@@ -0,0 +1,49 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Msm
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Merge merge modules into an MSI file.
10 /// </summary>
11 [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")]
12 public class MsmMerge2
13 {
14 }
15
16 /// <summary>
17 /// Defines the standard COM IClassFactory interface.
18 /// </summary>
19 [ComImport, Guid("00000001-0000-0000-C000-000000000046")]
20 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
21 public interface IClassFactory
22 {
23 /// <summary>
24 ///
25 /// </summary>
26 [return: MarshalAs(UnmanagedType.IUnknown)]
27 object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
28 }
29
30 /// <summary>
31 /// Contains native methods for merge operations.
32 /// </summary>
33 public static class MsmInterop
34 {
35 [DllImport("mergemod.dll", EntryPoint = "DllGetClassObject", PreserveSig = false)]
36 [return: MarshalAs(UnmanagedType.IUnknown)]
37 private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
38
39 /// <summary>
40 /// Load the merge object directly from a local mergemod.dll without going through COM registration.
41 /// </summary>
42 /// <returns>Merge interface.</returns>
43 public static IMsmMerge2 GetMsmMerge()
44 {
45 var classFactory = (IClassFactory)MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID);
46 return (IMsmMerge2)classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID);
47 }
48 }
49}
diff --git a/src/wix/WixToolset.Core.Native/Ole32/Storage.cs b/src/wix/WixToolset.Core.Native/Ole32/Storage.cs
new file mode 100644
index 00000000..3e4c6af2
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Ole32/Storage.cs
@@ -0,0 +1,377 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Ole32
4{
5 using System;
6 using System.Runtime.InteropServices;
7 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
8 using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
9
10 /// <summary>
11 /// Wrapper for the compound storage file APIs.
12 /// </summary>
13 internal class Storage : IDisposable
14 {
15 private readonly IStorage storage;
16 private bool disposed;
17
18 /// <summary>
19 /// Instantiate a new Storage.
20 /// </summary>
21 /// <param name="storage">The native storage interface.</param>
22 private Storage(IStorage storage)
23 {
24 this.storage = storage;
25 }
26
27 /// <summary>
28 /// Storage destructor.
29 /// </summary>
30 ~Storage()
31 {
32 this.Dispose();
33 }
34
35 /// <summary>
36 /// The IEnumSTATSTG interface enumerates an array of STATSTG structures.
37 /// </summary>
38 [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
39 private interface IEnumSTATSTG
40 {
41 /// <summary>
42 /// Gets a specified number of STATSTG structures.
43 /// </summary>
44 /// <param name="celt">The number of STATSTG structures requested.</param>
45 /// <param name="rgelt">An array of STATSTG structures returned.</param>
46 /// <param name="pceltFetched">The number of STATSTG structures retrieved in the rgelt parameter.</param>
47 /// <returns>The error code.</returns>
48 [PreserveSig]
49 uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched);
50
51 /// <summary>
52 /// Skips a specified number of STATSTG structures in the enumeration sequence.
53 /// </summary>
54 /// <param name="celt">The number of STATSTG structures to skip.</param>
55 void Skip(uint celt);
56
57 /// <summary>
58 /// Resets the enumeration sequence to the beginning of the STATSTG structure array.
59 /// </summary>
60 void Reset();
61
62 /// <summary>
63 /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator.
64 /// </summary>
65 /// <returns>The cloned IEnumSTATSTG interface.</returns>
66 [return: MarshalAs(UnmanagedType.Interface)]
67 IEnumSTATSTG Clone();
68 }
69
70 /// <summary>
71 /// The IStorage interface supports the creation and management of structured storage objects.
72 /// </summary>
73 [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
74 private interface IStorage
75 {
76 /// <summary>
77 /// Creates and opens a stream object with the specified name contained in this storage object.
78 /// </summary>
79 /// <param name="pwcsName">The name of the newly created stream.</param>
80 /// <param name="grfMode">Specifies the access mode to use when opening the newly created stream.</param>
81 /// <param name="reserved1">Reserved for future use; must be zero.</param>
82 /// <param name="reserved2">Reserved for future use; must be zero.</param>
83 /// <param name="ppstm">On return, pointer to the location of the new IStream interface pointer.</param>
84 void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm);
85
86 /// <summary>
87 /// Opens an existing stream object within this storage object using the specified access permissions in grfMode.
88 /// </summary>
89 /// <param name="pwcsName">The name of the stream to open.</param>
90 /// <param name="reserved1">Reserved for future use; must be NULL.</param>
91 /// <param name="grfMode">Specifies the access mode to be assigned to the open stream.</param>
92 /// <param name="reserved2">Reserved for future use; must be zero.</param>
93 /// <param name="ppstm">A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object.</param>
94 void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm);
95
96 /// <summary>
97 /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode.
98 /// </summary>
99 /// <param name="pwcsName">The name of the newly created storage object.</param>
100 /// <param name="grfMode">A value that specifies the access mode to use when opening the newly created storage object.</param>
101 /// <param name="reserved1">Reserved for future use; must be zero.</param>
102 /// <param name="reserved2">Reserved for future use; must be zero.</param>
103 /// <param name="ppstg">A pointer, when successful, to the location of the IStorage pointer to the newly created storage object.</param>
104 void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg);
105
106 /// <summary>
107 /// Opens an existing storage object with the specified name in the specified access mode.
108 /// </summary>
109 /// <param name="pwcsName">The name of the storage object to open.</param>
110 /// <param name="pstgPriority">Must be NULL.</param>
111 /// <param name="grfMode">Specifies the access mode to use when opening the storage object.</param>
112 /// <param name="snbExclude">Must be NULL.</param>
113 /// <param name="reserved">Reserved for future use; must be zero.</param>
114 /// <param name="ppstg">When successful, pointer to the location of an IStorage pointer to the opened storage object.</param>
115 void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg);
116
117 /// <summary>
118 /// Copies the entire contents of an open storage object to another storage object.
119 /// </summary>
120 /// <param name="ciidExclude">The number of elements in the array pointed to by rgiidExclude.</param>
121 /// <param name="rgiidExclude">An array of interface identifiers (IIDs) that either the caller knows about and does not want
122 /// copied or that the storage object does not support, but whose state the caller will later explicitly copy.</param>
123 /// <param name="snbExclude">A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination.</param>
124 /// <param name="pstgDest">A pointer to the open storage object into which this storage object is to be copied.</param>
125 void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest);
126
127 /// <summary>
128 /// Copies or moves a substorage or stream from this storage object to another storage object.
129 /// </summary>
130 /// <param name="pwcsName">The name of the element in this storage object to be moved or copied.</param>
131 /// <param name="pstgDest">IStorage pointer to the destination storage object.</param>
132 /// <param name="pwcsNewName">The new name for the element in its new storage object.</param>
133 /// <param name="grfFlags">Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY).</param>
134 void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags);
135
136 /// <summary>
137 /// Reflects changes for a transacted storage object to the parent level.
138 /// </summary>
139 /// <param name="grfCommitFlags">Controls how the changes are committed to the storage object.</param>
140 void Commit(uint grfCommitFlags);
141
142 /// <summary>
143 /// Discards all changes that have been made to the storage object since the last commit operation.
144 /// </summary>
145 void Revert();
146
147 /// <summary>
148 /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object.
149 /// </summary>
150 /// <param name="reserved1">Reserved for future use; must be zero.</param>
151 /// <param name="reserved2">Reserved for future use; must be NULL.</param>
152 /// <param name="reserved3">Reserved for future use; must be zero.</param>
153 /// <param name="ppenum">Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object.</param>
154 void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum);
155
156 /// <summary>
157 /// Removes the specified storage or stream from this storage object.
158 /// </summary>
159 /// <param name="pwcsName">The name of the storage or stream to be removed.</param>
160 void DestroyElement(string pwcsName);
161
162 /// <summary>
163 /// Renames the specified storage or stream in this storage object.
164 /// </summary>
165 /// <param name="pwcsOldName">The name of the substorage or stream to be changed.</param>
166 /// <param name="pwcsNewName">The new name for the specified substorage or stream.</param>
167 void RenameElement(string pwcsOldName, string pwcsNewName);
168
169 /// <summary>
170 /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system.
171 /// </summary>
172 /// <param name="pwcsName">The name of the storage object element whose times are to be modified.</param>
173 /// <param name="pctime">Either the new creation time for the element or NULL if the creation time is not to be modified.</param>
174 /// <param name="patime">Either the new access time for the element or NULL if the access time is not to be modified.</param>
175 /// <param name="pmtime">Either the new modification time for the element or NULL if the modification time is not to be modified.</param>
176 void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime);
177
178 /// <summary>
179 /// Assigns the specified CLSID to this storage object.
180 /// </summary>
181 /// <param name="clsid">The CLSID that is to be associated with the storage object.</param>
182 void SetClass(Guid clsid);
183
184 /// <summary>
185 /// Stores up to 32 bits of state information in this storage object.
186 /// </summary>
187 /// <param name="grfStateBits">Specifies the new values of the bits to set.</param>
188 /// <param name="grfMask">A binary mask indicating which bits in grfStateBits are significant in this call.</param>
189 void SetStateBits(uint grfStateBits, uint grfMask);
190
191 /// <summary>
192 /// Returns the STATSTG structure for this open storage object.
193 /// </summary>
194 /// <param name="pstatstg">On return, pointer to a STATSTG structure where this method places information about the open storage object.</param>
195 /// <param name="grfStatFlag">Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation.</param>
196 void Stat(out STATSTG pstatstg, uint grfStatFlag);
197 }
198
199 /// <summary>
200 /// The IStream interface lets you read and write data to stream objects.
201 /// </summary>
202 [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
203 private interface IStream
204 {
205 /// <summary>
206 /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer.
207 /// </summary>
208 /// <param name="pv">A pointer to the buffer which the stream data is read into.</param>
209 /// <param name="cb">The number of bytes of data to read from the stream object.</param>
210 /// <param name="pcbRead">A pointer to a ULONG variable that receives the actual number of bytes read from the stream object.</param>
211 void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead);
212
213 /// <summary>
214 /// Writes a specified number of bytes into the stream object starting at the current seek pointer.
215 /// </summary>
216 /// <param name="pv">A pointer to the buffer that contains the data that is to be written to the stream.</param>
217 /// <param name="cb">The number of bytes of data to attempt to write into the stream.</param>
218 /// <param name="pcbWritten">A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object.</param>
219 void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten);
220
221 /// <summary>
222 /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer.
223 /// </summary>
224 /// <param name="dlibMove">The displacement to be added to the location indicated by the dwOrigin parameter.</param>
225 /// <param name="dwOrigin">The origin for the displacement specified in dlibMove.</param>
226 /// <param name="plibNewPosition">A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream.</param>
227 void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition);
228
229 /// <summary>
230 /// Changes the size of the stream object.
231 /// </summary>
232 /// <param name="libNewSize">Specifies the new size of the stream as a number of bytes.</param>
233 void SetSize(long libNewSize);
234
235 /// <summary>
236 /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream.
237 /// </summary>
238 /// <param name="pstm">A pointer to the destination stream.</param>
239 /// <param name="cb">The number of bytes to copy from the source stream.</param>
240 /// <param name="pcbRead">A pointer to the location where this method writes the actual number of bytes read from the source.</param>
241 /// <param name="pcbWritten">A pointer to the location where this method writes the actual number of bytes written to the destination.</param>
242 void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten);
243
244 /// <summary>
245 /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object.
246 /// </summary>
247 /// <param name="grfCommitFlags">Controls how the changes for the stream object are committed.</param>
248 void Commit(int grfCommitFlags);
249
250 /// <summary>
251 /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit.
252 /// </summary>
253 void Revert();
254
255 /// <summary>
256 /// Restricts access to a specified range of bytes in the stream.
257 /// </summary>
258 /// <param name="libOffset">Integer that specifies the byte offset for the beginning of the range.</param>
259 /// <param name="cb">Integer that specifies the length of the range, in bytes, to be restricted.</param>
260 /// <param name="dwLockType">Specifies the restrictions being requested on accessing the range.</param>
261 void LockRegion(long libOffset, long cb, int dwLockType);
262
263 /// <summary>
264 /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion.
265 /// </summary>
266 /// <param name="libOffset">Specifies the byte offset for the beginning of the range.</param>
267 /// <param name="cb">Specifies, in bytes, the length of the range to be restricted.</param>
268 /// <param name="dwLockType">Specifies the access restrictions previously placed on the range.</param>
269 void UnlockRegion(long libOffset, long cb, int dwLockType);
270
271 /// <summary>
272 /// Retrieves the STATSTG structure for this stream.
273 /// </summary>
274 /// <param name="pstatstg">Pointer to a STATSTG structure where this method places information about this stream object.</param>
275 /// <param name="grfStatFlag">Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation.</param>
276 void Stat(out STATSTG pstatstg, int grfStatFlag);
277
278 /// <summary>
279 /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes.
280 /// </summary>
281 /// <param name="ppstm">When successful, pointer to the location of an IStream pointer to the new stream object.</param>
282 void Clone(out IStream ppstm);
283 }
284
285 /// <summary>
286 /// Creates a new compound file storage object.
287 /// </summary>
288 /// <param name="storageFile">The compound file being created.</param>
289 /// <param name="mode">Specifies the access mode to use when opening the new storage object.</param>
290 /// <returns>The created Storage object.</returns>
291 public static Storage CreateDocFile(string storageFile, StorageMode mode)
292 {
293 var storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0);
294
295 return new Storage(storage);
296 }
297
298 /// <summary>
299 /// Opens an existing root storage object in the file system.
300 /// </summary>
301 /// <param name="storageFile">The file that contains the storage object to open.</param>
302 /// <param name="mode">Specifies the access mode to use to open the storage object.</param>
303 /// <returns>The created Storage object.</returns>
304 public static Storage Open(string storageFile, StorageMode mode)
305 {
306 var storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0);
307
308 return new Storage(storage);
309 }
310
311 /// <summary>
312 /// Copies the entire contents of this open storage object into another Storage object.
313 /// </summary>
314 /// <param name="destinationStorage">The destination Storage object.</param>
315 public void CopyTo(Storage destinationStorage)
316 {
317 this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage);
318 }
319
320 /// <summary>
321 /// Opens an existing Storage object with the specified name according to the specified access mode.
322 /// </summary>
323 /// <param name="name">The name of the Storage object.</param>
324 /// <returns>The opened Storage object.</returns>
325 public Storage OpenStorage(string name)
326 {
327 this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out var subStorage);
328
329 return new Storage(subStorage);
330 }
331
332 /// <summary>
333 /// Disposes the managed and unmanaged objects in this object.
334 /// </summary>
335 public void Dispose()
336 {
337 if (!this.disposed)
338 {
339 Marshal.ReleaseComObject(this.storage);
340
341 this.disposed = true;
342 }
343
344 GC.SuppressFinalize(this);
345 }
346
347 /// <summary>
348 /// The native methods.
349 /// </summary>
350 private static class NativeMethods
351 {
352 /// <summary>
353 /// Creates a new compound file storage object.
354 /// </summary>
355 /// <param name="pwcsName">The name for the compound file being created.</param>
356 /// <param name="grfMode">Specifies the access mode to use when opening the new storage object.</param>
357 /// <param name="reserved">Reserved for future use; must be zero.</param>
358 /// <returns>A pointer to the location of the IStorage pointer to the new storage object.</returns>
359 [DllImport("ole32.dll", PreserveSig = false)]
360 [return: MarshalAs(UnmanagedType.Interface)]
361 internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved);
362
363 /// <summary>
364 /// Opens an existing root storage object in the file system.
365 /// </summary>
366 /// <param name="pwcsName">The file that contains the storage object to open.</param>
367 /// <param name="pstgPriority">Most often NULL.</param>
368 /// <param name="grfMode">Specifies the access mode to use to open the storage object.</param>
369 /// <param name="snbExclude">If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened.</param>
370 /// <param name="reserved">Indicates reserved for future use; must be zero.</param>
371 /// <returns>A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage.</returns>
372 [DllImport("ole32.dll", PreserveSig = false)]
373 [return: MarshalAs(UnmanagedType.Interface)]
374 internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved);
375 }
376 }
377}
diff --git a/src/wix/WixToolset.Core.Native/Ole32/StorageMode.cs b/src/wix/WixToolset.Core.Native/Ole32/StorageMode.cs
new file mode 100644
index 00000000..24b60e4d
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/Ole32/StorageMode.cs
@@ -0,0 +1,55 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.Ole32
4{
5 /// <summary>
6 /// Specifies the access mode to use when opening, creating, or deleting a storage object.
7 /// </summary>
8 internal enum StorageMode
9 {
10 /// <summary>
11 /// Indicates that the object is read-only, meaning that modifications cannot be made.
12 /// </summary>
13 Read = 0x0,
14
15 /// <summary>
16 /// Enables you to save changes to the object, but does not permit access to its data.
17 /// </summary>
18 Write = 0x1,
19
20 /// <summary>
21 /// Enables access and modification of object data.
22 /// </summary>
23 ReadWrite = 0x2,
24
25 /// <summary>
26 /// Specifies that subsequent openings of the object are not denied read or write access.
27 /// </summary>
28 ShareDenyNone = 0x40,
29
30 /// <summary>
31 /// Prevents others from subsequently opening the object in Read mode.
32 /// </summary>
33 ShareDenyRead = 0x30,
34
35 /// <summary>
36 /// Prevents others from subsequently opening the object for Write or ReadWrite access.
37 /// </summary>
38 ShareDenyWrite = 0x20,
39
40 /// <summary>
41 /// Prevents others from subsequently opening the object in any mode.
42 /// </summary>
43 ShareExclusive = 0x10,
44
45 /// <summary>
46 /// Opens the storage object with exclusive access to the most recently committed version.
47 /// </summary>
48 Priority = 0x40000,
49
50 /// <summary>
51 /// Indicates that an existing storage object or stream should be removed before the new object replaces it.
52 /// </summary>
53 Create = 0x1000,
54 }
55}
diff --git a/src/wix/WixToolset.Core.Native/PatchAPI/PatchInterop.cs b/src/wix/WixToolset.Core.Native/PatchAPI/PatchInterop.cs
new file mode 100644
index 00000000..04f5a553
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/PatchAPI/PatchInterop.cs
@@ -0,0 +1,990 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native.PatchAPI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data.Symbols;
11
12 /// <summary>
13 /// Interop class for the mspatchc.dll.
14 /// </summary>
15 internal static class PatchInterop
16 {
17 // From WinError.h in the Platform SDK
18 internal const ushort FACILITY_WIN32 = 7;
19
20 /// <summary>
21 /// Parse a number from text in either hex or decimal.
22 /// </summary>
23 /// <param name="source">Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise.</param>
24 /// <returns>Numeric value that source represents.</returns>
25 internal static uint ParseHexOrDecimal(string source)
26 {
27 var value = source.Trim();
28 if (String.Equals(value.Substring(0, 2), "0x", StringComparison.OrdinalIgnoreCase))
29 {
30 return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat);
31 }
32 else
33 {
34 return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat);
35 }
36 }
37
38 /// <summary>
39 /// Create a binary delta file.
40 /// </summary>
41 /// <param name="deltaFile">Name of the delta file to create.</param>
42 /// <param name="targetFile">Name of updated file.</param>
43 /// <param name="targetSymbolPath">Optional paths to updated file's symbols.</param>
44 /// <param name="targetRetainOffsets">Optional offsets to the delta retain sections in the updated file.</param>
45 /// <param name="basisFiles">Optional array of target files.</param>
46 /// <param name="basisSymbolPaths">Optional array of target files' symbol paths (must match basisFiles array).</param>
47 /// <param name="basisIgnoreLengths">Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries).</param>
48 /// <param name="basisIgnoreOffsets">Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries).</param>
49 /// <param name="basisRetainLengths">Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries).</param>
50 /// <param name="basisRetainOffsets">Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries).</param>
51 /// <param name="apiPatchingSymbolFlags">ApiPatchingSymbolFlags value.</param>
52 /// <param name="optimizePatchSizeForLargeFiles">OptimizePatchSizeForLargeFiles value.</param>
53 /// <param name="retainRangesIgnored">Flag to indicate retain ranges were ignored due to mismatch.</param>
54 /// <returns>true if delta file was created, false if whole file should be used instead.</returns>
55 public static bool CreateDelta(
56 string deltaFile,
57 string targetFile,
58 string targetSymbolPath,
59 string targetRetainOffsets,
60 string[] basisFiles,
61 string[] basisSymbolPaths,
62 string[] basisIgnoreLengths,
63 string[] basisIgnoreOffsets,
64 string[] basisRetainLengths,
65 string[] basisRetainOffsets,
66 PatchSymbolFlags apiPatchingSymbolFlags,
67 bool optimizePatchSizeForLargeFiles,
68 out bool retainRangesIgnored
69 )
70 {
71 retainRangesIgnored = false;
72 if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlags.PatchSymbolNoImagehlp | PatchSymbolFlags.PatchSymbolNoFailures | PatchSymbolFlags.PatchSymbolUndecoratedToo)))
73 {
74 throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags");
75 }
76
77 if (null == deltaFile || 0 == deltaFile.Length)
78 {
79 throw new ArgumentNullException("deltaFile");
80 }
81
82 if (null == targetFile || 0 == targetFile.Length)
83 {
84 throw new ArgumentNullException("targetFile");
85 }
86
87 if (null == basisFiles || 0 == basisFiles.Length)
88 {
89 return false;
90 }
91 var countOldFiles = (uint)basisFiles.Length;
92
93 if (null != basisSymbolPaths)
94 {
95 if (0 != basisSymbolPaths.Length)
96 {
97 if ((uint)basisSymbolPaths.Length != countOldFiles)
98 {
99 throw new ArgumentOutOfRangeException("basisSymbolPaths");
100 }
101 }
102 }
103 // a null basisSymbolPaths is allowed.
104
105 if (null != basisIgnoreLengths)
106 {
107 if (0 != basisIgnoreLengths.Length)
108 {
109 if ((uint)basisIgnoreLengths.Length != countOldFiles)
110 {
111 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
112 }
113 }
114 }
115 else
116 {
117 basisIgnoreLengths = new string[countOldFiles];
118 }
119
120 if (null != basisIgnoreOffsets)
121 {
122 if (0 != basisIgnoreOffsets.Length)
123 {
124 if ((uint)basisIgnoreOffsets.Length != countOldFiles)
125 {
126 throw new ArgumentOutOfRangeException("basisIgnoreOffsets");
127 }
128 }
129 }
130 else
131 {
132 basisIgnoreOffsets = new string[countOldFiles];
133 }
134
135 if (null != basisRetainLengths)
136 {
137 if (0 != basisRetainLengths.Length)
138 {
139 if ((uint)basisRetainLengths.Length != countOldFiles)
140 {
141 throw new ArgumentOutOfRangeException("basisRetainLengths");
142 }
143 }
144 }
145 else
146 {
147 basisRetainLengths = new string[countOldFiles];
148 }
149
150 if (null != basisRetainOffsets)
151 {
152 if (0 != basisRetainOffsets.Length)
153 {
154 if ((uint)basisRetainOffsets.Length != countOldFiles)
155 {
156 throw new ArgumentOutOfRangeException("basisRetainOffsets");
157 }
158 }
159 }
160 else
161 {
162 basisRetainOffsets = new string[countOldFiles];
163 }
164
165 var pod = new PatchOptionData();
166 pod.symbolOptionFlags = apiPatchingSymbolFlags;
167 pod.newFileSymbolPath = targetSymbolPath;
168 pod.oldFileSymbolPathArray = basisSymbolPaths;
169 pod.extendedOptionFlags = 0;
170 var oldFileInfoArray = new PatchOldFileInfoW[countOldFiles];
171 var newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(','));
172 for (uint i = 0; i < countOldFiles; ++i)
173 {
174 var ofi = new PatchOldFileInfoW();
175 ofi.oldFileName = basisFiles[i];
176 var ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(','));
177 var ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(','));
178 var retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(','));
179 var retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(','));
180 // Validate inputs
181 if (ignoreLengthArray.Length != ignoreOffsetArray.Length)
182 {
183 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
184 }
185
186 if (retainLengthArray.Length != retainOffsetArray.Length)
187 {
188 throw new ArgumentOutOfRangeException("basisRetainLengths");
189 }
190
191 if (newRetainOffsetArray.Length != retainOffsetArray.Length)
192 {
193 // remove all retain range information
194 retainRangesIgnored = true;
195 for (uint j = 0; j < countOldFiles; ++j)
196 {
197 basisRetainLengths[j] = null;
198 basisRetainOffsets[j] = null;
199 }
200 retainLengthArray = new string[0];
201 retainOffsetArray = new string[0];
202 newRetainOffsetArray = new string[0];
203 for (uint j = 0; j < oldFileInfoArray.Length; ++j)
204 {
205 oldFileInfoArray[j].retainRange = null;
206 }
207 }
208
209 // Populate IgnoreRange structure
210 PatchIgnoreRange[] ignoreArray = null;
211 if (0 != ignoreLengthArray.Length)
212 {
213 ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length];
214 for (var j = 0; j < ignoreLengthArray.Length; ++j)
215 {
216 var ignoreRange = new PatchIgnoreRange();
217 ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]);
218 ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]);
219 ignoreArray[j] = ignoreRange;
220 }
221 ofi.ignoreRange = ignoreArray;
222 }
223
224 PatchRetainRange[] retainArray = null;
225 if (0 != newRetainOffsetArray.Length)
226 {
227 retainArray = new PatchRetainRange[retainLengthArray.Length];
228 for (var j = 0; j < newRetainOffsetArray.Length; ++j)
229 {
230 var retainRange = new PatchRetainRange();
231 retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]);
232 retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]);
233 retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]);
234 retainArray[j] = retainRange;
235 }
236 ofi.retainRange = retainArray;
237 }
238 oldFileInfoArray[i] = ofi;
239 }
240
241 if (CreatePatchFileExW(
242 countOldFiles,
243 oldFileInfoArray,
244 targetFile,
245 deltaFile,
246 PatchOptionFlags(optimizePatchSizeForLargeFiles),
247 pod,
248 null,
249 IntPtr.Zero))
250 {
251 return true;
252 }
253
254 // determine if this is an error or a need to use whole file.
255 var err = Marshal.GetLastWin32Error();
256 switch (err)
257 {
258 case unchecked((int)ERROR_PATCH_BIGGER_THAN_COMPRESSED):
259 break;
260
261 // too late to exclude this file -- should have been caught before
262 case unchecked((int)ERROR_PATCH_SAME_FILE):
263 default:
264 throw new System.ComponentModel.Win32Exception(err);
265 }
266 return false;
267 }
268
269 /// <summary>
270 /// Extract the delta header.
271 /// </summary>
272 /// <param name="delta">Name of delta file.</param>
273 /// <param name="deltaHeader">Name of file to create with the delta's header.</param>
274 static public void ExtractDeltaHeader(string delta, string deltaHeader)
275 {
276 if (!ExtractPatchHeaderToFileW(delta, deltaHeader))
277 {
278 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
279 }
280 }
281
282 /// <summary>
283 /// Returns the PatchOptionFlags to use.
284 /// </summary>
285 /// <param name="optimizeForLargeFiles">True if optimizing for large files.</param>
286 /// <returns>PATCH_OPTION_FLAG values</returns>
287 static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles)
288 {
289 var flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST;
290 if (optimizeForLargeFiles)
291 {
292 flags |= PATCH_OPTION_USE_LZX_LARGE;
293 }
294 return flags;
295 }
296
297 //---------------------------------------------------------------------
298 // From PatchApi.h
299 //---------------------------------------------------------------------
300
301 //
302 // The following contants can be combined and used as the OptionFlags
303 // parameter in the patch creation apis.
304
305 internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower)
306
307 internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large)
308 internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal
309 internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries
310 internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer)
311
312 internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports
313 internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks
314 internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image
315 internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same
316 internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower)
317 internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero
318 internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps
319 internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch
320 internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support)
321 internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer)
322 internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally)
323
324 internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007;
325
326 //
327 // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags:
328 //
329
330 internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer)
331 internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer)
332
333 //
334 // In addition to the standard Win32 error codes, the following error codes may
335 // be returned via GetLastError() when one of the patch APIs fails.
336
337 internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create
338 internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create
339 internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create
340 internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create
341 internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create
342 internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create
343
344 /// <summary>
345 /// Delegate type that the PatchAPI calls for progress notification.
346 /// </summary>
347 /// <param name="context">.</param>
348 /// <param name="currentPosition">.</param>
349 /// <param name="maxPosition">.</param>
350 /// <returns>True for success</returns>
351 public delegate bool PatchProgressCallback(
352 IntPtr context,
353 uint currentPosition,
354 uint maxPosition
355 );
356
357 /// <summary>
358 /// Delegate type that the PatchAPI calls for patch symbol load information.
359 /// </summary>
360 /// <param name="whichFile">.</param>
361 /// <param name="symbolFileName">.</param>
362 /// <param name="symType">.</param>
363 /// <param name="symbolFileCheckSum">.</param>
364 /// <param name="symbolFileTimeDate">.</param>
365 /// <param name="imageFileCheckSum">.</param>
366 /// <param name="imageFileTimeDate">.</param>
367 /// <param name="context">.</param>
368 /// <returns>???</returns>
369 public delegate bool PatchSymloadCallback(
370 uint whichFile, // 0 for new file, 1 for first old file, etc
371 [MarshalAs(UnmanagedType.LPStr)] string symbolFileName,
372 uint symType, // see SYM_TYPE in imagehlp.h
373 uint symbolFileCheckSum,
374 uint symbolFileTimeDate,
375 uint imageFileCheckSum,
376 uint imageFileTimeDate,
377 IntPtr context
378 );
379
380 /// <summary>
381 /// Wraps PATCH_IGNORE_RANGE
382 /// </summary>
383 [StructLayout(LayoutKind.Sequential)]
384 internal class PatchIgnoreRange
385 {
386 public uint offsetInOldFile;
387 public uint lengthInBytes;
388 }
389
390 /// <summary>
391 /// Wraps PATCH_RETAIN_RANGE
392 /// </summary>
393 [StructLayout(LayoutKind.Sequential)]
394 internal class PatchRetainRange
395 {
396 public uint offsetInOldFile;
397 public uint lengthInBytes;
398 public uint offsetInNewFile;
399 }
400
401 /// <summary>
402 /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion)
403 /// </summary>
404 internal class PatchOldFileInfo
405 {
406 public PatchIgnoreRange[] ignoreRange;
407 public PatchRetainRange[] retainRange;
408 }
409
410 /// <summary>
411 /// Wraps PATCH_OLD_FILE_INFO_W
412 /// </summary>
413 internal class PatchOldFileInfoW : PatchOldFileInfo
414 {
415 public string oldFileName;
416 }
417
418 /// <summary>
419 /// Wraps each PATCH_INTERLEAVE_MAP Range
420 /// </summary>
421 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)]
422 internal class PatchInterleaveMapRange
423 {
424 public uint oldOffset;
425 public uint oldLength;
426 public uint newLength;
427 }
428
429 /// <summary>
430 /// Wraps PATCH_INTERLEAVE_MAP
431 /// </summary>
432 internal class PatchInterleaveMap
433 {
434 public PatchInterleaveMapRange[] ranges = null;
435 }
436
437
438 /// <summary>
439 /// Wraps PATCH_OPTION_DATA
440 /// </summary>
441 [BestFitMapping(false, ThrowOnUnmappableChar = true)]
442 internal class PatchOptionData
443 {
444 public PatchSymbolFlags symbolOptionFlags; // PATCH_SYMBOL_xxx flags
445 [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode
446 [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ]
447 public uint extendedOptionFlags;
448 public PatchSymloadCallback symLoadCallback = null;
449 public IntPtr symLoadContext = IntPtr.Zero;
450 public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer)
451 public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer)
452 }
453
454 //
455 // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode)
456 // path argument is available, even when used with one of the Unicode APIs
457 // such as CreatePatchFileW. This is because the unlerlying system services
458 // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names.
459 //
460
461 //
462 // A note about PATCH_RETAIN_RANGE specifiers with multiple old files:
463 //
464 // Each old version file must have the same RetainRangeCount, and the same
465 // retain range LengthInBytes and OffsetInNewFile values in the same order.
466 // Only the OffsetInOldFile values can differ between old foles for retain
467 // ranges.
468 //
469
470 //
471 // The following prototypes are (some of the) interfaces for creating patches from files.
472 //
473
474 /// <summary>
475 /// Creates a new delta.
476 /// </summary>
477 /// <param name="oldFileCount">Size of oldFileInfoArray.</param>
478 /// <param name="oldFileInfoArray">Target file information.</param>
479 /// <param name="newFileName">Name of updated file.</param>
480 /// <param name="patchFileName">Name of delta to create.</param>
481 /// <param name="optionFlags">PATCH_OPTION_xxx.</param>
482 /// <param name="optionData">Optional PATCH_OPTION_DATA structure.</param>
483 /// <param name="progressCallback">Delegate for progress callbacks.</param>
484 /// <param name="context">Context for progress callback delegate.</param>
485 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
486 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
487 [return: MarshalAs(UnmanagedType.Bool)]
488 internal static extern bool CreatePatchFileExW(
489 uint oldFileCount, // maximum 255
490 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")]
491 PatchOldFileInfoW[] oldFileInfoArray,
492 string newFileName, // input file (required)
493 string patchFileName, // output file (required)
494 uint optionFlags,
495 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")]
496 PatchOptionData optionData,
497 [MarshalAs (UnmanagedType.FunctionPtr)]
498 PatchProgressCallback progressCallback,
499 IntPtr context
500 );
501
502 /// <summary>
503 /// Extracts delta header from delta.
504 /// </summary>
505 /// <param name="patchFileName">Name of delta file.</param>
506 /// <param name="patchHeaderFileName">Name of file to create with delta header.</param>
507 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
508 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
509 [return: MarshalAs(UnmanagedType.Bool)]
510 internal static extern bool ExtractPatchHeaderToFileW(
511 string patchFileName, // input file
512 string patchHeaderFileName // output file
513 );
514
515 // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks
516
517 /// <summary>
518 /// Marshals arguments for the CreatePatch~ APIs
519 /// </summary>
520 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
521 internal class PatchAPIMarshaler : ICustomMarshaler
522 {
523 internal static ICustomMarshaler GetInstance(string cookie)
524 {
525 return new PatchAPIMarshaler(cookie);
526 }
527
528 private enum MarshalType
529 {
530 PATCH_OPTION_DATA,
531 PATCH_OLD_FILE_INFO_W
532 };
533
534 private readonly PatchAPIMarshaler.MarshalType marshalType;
535
536 private PatchAPIMarshaler(string cookie)
537 {
538 this.marshalType = (PatchAPIMarshaler.MarshalType)Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie);
539 }
540
541 //
542 // Summary:
543 // Returns the size of the native data to be marshaled.
544 //
545 // Returns:
546 // The size in bytes of the native data.
547 public int GetNativeDataSize()
548 {
549 return Marshal.SizeOf(typeof(IntPtr));
550 }
551
552 //
553 // Summary:
554 // Performs necessary cleanup of the managed data when it is no longer needed.
555 //
556 // Parameters:
557 // ManagedObj:
558 // The managed object to be destroyed.
559 public void CleanUpManagedData(object ManagedObj)
560 {
561 }
562
563 //
564 // Summary:
565 // Performs necessary cleanup of the unmanaged data when it is no longer needed.
566 //
567 // Parameters:
568 // pNativeData:
569 // A pointer to the unmanaged data to be destroyed.
570 public void CleanUpNativeData(IntPtr pNativeData)
571 {
572 if (IntPtr.Zero == pNativeData)
573 {
574 return;
575 }
576
577 switch (this.marshalType)
578 {
579 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
580 this.CleanUpPOD(pNativeData);
581 break;
582 default:
583 this.CleanUpPOFI_A(pNativeData);
584 break;
585 }
586 }
587
588 //
589 // Summary:
590 // Converts the managed data to unmanaged data.
591 //
592 // Parameters:
593 // ManagedObj:
594 // The managed object to be converted.
595 //
596 // Returns:
597 // Returns the COM view of the managed object.
598 public IntPtr MarshalManagedToNative(object ManagedObj)
599 {
600 if (null == ManagedObj)
601 {
602 return IntPtr.Zero;
603 }
604
605 switch (this.marshalType)
606 {
607 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
608 return this.MarshalPOD(ManagedObj as PatchOptionData);
609 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
610 return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]);
611 default:
612 throw new InvalidOperationException();
613 }
614 }
615
616
617 //
618 // Summary:
619 // Converts the unmanaged data to managed data.
620 //
621 // Parameters:
622 // pNativeData:
623 // A pointer to the unmanaged data to be wrapped.
624 //
625 // Returns:
626 // Returns the managed view of the COM data.
627 public object MarshalNativeToManaged(IntPtr pNativeData)
628 {
629 return null;
630 }
631
632 // Implementation *************************************************
633
634 // PATCH_OPTION_DATA offsets
635 private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32));
636 private static readonly int newFileSymbolPathOffset = 2 * Marshal.SizeOf(typeof(Int32));
637 private static readonly int oldFileSymbolPathArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
638 private static readonly int extendedOptionFlagsOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
639 private static readonly int symLoadCallbackOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
640 private static readonly int symLoadContextOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr));
641 private static readonly int interleaveMapArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 4 * Marshal.SizeOf(typeof(IntPtr));
642 private static readonly int maxLzxWindowSizeOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr));
643 private static readonly int patchOptionDataSize = 4 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr));
644
645 // PATCH_OLD_FILE_INFO offsets
646 private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32));
647 private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
648 private static readonly int ignoreRangeArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
649 private static readonly int retainRangeCountOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
650 private static readonly int retainRangeArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr));
651 private static readonly int patchOldFileInfoSize = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr));
652
653 // Methods and data used to preserve data needed for cleanup
654
655 // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount)
656 private static readonly Dictionary<IntPtr, int> OldFileCounts = new Dictionary<IntPtr, int>();
657 private static readonly object OldFileCountsLock = new object();
658
659 private IntPtr CreateMainStruct(int oldFileCount)
660 {
661 int nativeSize;
662 switch (this.marshalType)
663 {
664 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
665 nativeSize = patchOptionDataSize;
666 break;
667 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
668 nativeSize = oldFileCount * patchOldFileInfoSize;
669 break;
670 default:
671 throw new InvalidOperationException();
672 }
673
674 var native = Marshal.AllocCoTaskMem(nativeSize);
675
676 lock (PatchAPIMarshaler.OldFileCountsLock)
677 {
678 PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount);
679 }
680
681 return native;
682 }
683
684 private static void ReleaseMainStruct(IntPtr native)
685 {
686 lock (PatchAPIMarshaler.OldFileCountsLock)
687 {
688 PatchAPIMarshaler.OldFileCounts.Remove(native);
689 }
690 Marshal.FreeCoTaskMem(native);
691 }
692
693 private static int GetOldFileCount(IntPtr native)
694 {
695 lock (PatchAPIMarshaler.OldFileCountsLock)
696 {
697 return PatchAPIMarshaler.OldFileCounts[native];
698 }
699 }
700
701 // Helper methods
702
703 private static IntPtr OptionalAnsiString(string managed)
704 {
705 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed);
706 }
707
708 private static IntPtr OptionalUnicodeString(string managed)
709 {
710 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed);
711 }
712
713 // string array must be of the same length as the number of old files
714 private static IntPtr CreateArrayOfStringA(string[] managed)
715 {
716 if (null == managed)
717 {
718 return IntPtr.Zero;
719 }
720
721 var size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
722 var native = Marshal.AllocCoTaskMem(size);
723
724 for (var i = 0; i < managed.Length; ++i)
725 {
726 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i]));
727 }
728
729 return native;
730 }
731
732 // string array must be of the same length as the number of old files
733 private static IntPtr CreateArrayOfStringW(string[] managed)
734 {
735 if (null == managed)
736 {
737 return IntPtr.Zero;
738 }
739
740 var size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
741 var native = Marshal.AllocCoTaskMem(size);
742
743 for (var i = 0; i < managed.Length; ++i)
744 {
745 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i]));
746 }
747
748 return native;
749 }
750
751 private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed)
752 {
753 if (null == managed)
754 {
755 return IntPtr.Zero;
756 }
757
758 if (null == managed.ranges)
759 {
760 return IntPtr.Zero;
761 }
762
763 if (0 == managed.ranges.Length)
764 {
765 return IntPtr.Zero;
766 }
767
768 var native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32))
769 + managed.ranges.Length * (Marshal.SizeOf(typeof(PatchInterleaveMap))));
770 WriteUInt32(native, (uint)managed.ranges.Length);
771
772 for (var i = 0; i < managed.ranges.Length; ++i)
773 {
774 Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i * Marshal.SizeOf(typeof(PatchInterleaveMap))), false);
775 }
776 return native;
777 }
778
779 private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed)
780 {
781 if (null == managed)
782 {
783 return IntPtr.Zero;
784 }
785
786 var native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr)));
787
788 for (var i = 0; i < managed.Length; ++i)
789 {
790 Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i]));
791 }
792
793 return native;
794 }
795
796 private static void WriteUInt32(IntPtr native, uint data)
797 {
798 Marshal.WriteInt32(native, unchecked((int)data));
799 }
800
801 private static void WriteUInt32(IntPtr native, int offset, uint data)
802 {
803 Marshal.WriteInt32(native, offset, unchecked((int)data));
804 }
805
806 // Marshal operations
807
808 private IntPtr MarshalPOD(PatchOptionData managed)
809 {
810 if (null == managed)
811 {
812 throw new ArgumentNullException("managed");
813 }
814
815 var native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length);
816 Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct
817 WriteUInt32(native, symbolOptionFlagsOffset, (uint)managed.symbolOptionFlags);
818 Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath));
819 Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray));
820 WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags);
821
822 // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null.
823 if (null == managed.symLoadCallback)
824 {
825 Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero);
826 }
827 else
828 {
829 Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback));
830 }
831
832 Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext);
833 Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray));
834 WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize);
835 return native;
836 }
837
838 private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed)
839 {
840 if (null == managed)
841 {
842 throw new ArgumentNullException("managed");
843 }
844
845 if (0 == managed.Length)
846 {
847 return IntPtr.Zero;
848 }
849
850 var native = this.CreateMainStruct(managed.Length);
851
852 for (var i = 0; i < managed.Length; ++i)
853 {
854 PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize));
855 }
856
857 return native;
858 }
859
860 private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native)
861 {
862 PatchAPIMarshaler.MarshalPOFI(managed, native);
863 Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName
864 }
865
866 private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native)
867 {
868 Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct
869 WriteUInt32(native, ignoreRangeCountOffset,
870 (null == managed.ignoreRange) ? 0 : (uint)managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255
871 Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray
872 WriteUInt32(native, retainRangeCountOffset,
873 (null == managed.retainRange) ? 0 : (uint)managed.retainRange.Length); // RetainRangeCount // maximum 255
874 Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray
875 }
876
877 private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array)
878 {
879 if (null == array)
880 {
881 return IntPtr.Zero;
882 }
883
884 if (0 == array.Length)
885 {
886 return IntPtr.Zero;
887 }
888
889 var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchIgnoreRange)));
890
891 for (var i = 0; i < array.Length; ++i)
892 {
893 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchIgnoreRange)))), false);
894 }
895
896 return native;
897 }
898
899 private static IntPtr MarshalPRRArray(PatchRetainRange[] array)
900 {
901 if (null == array)
902 {
903 return IntPtr.Zero;
904 }
905
906 if (0 == array.Length)
907 {
908 return IntPtr.Zero;
909 }
910
911 var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchRetainRange)));
912
913 for (var i = 0; i < array.Length; ++i)
914 {
915 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchRetainRange)))), false);
916 }
917
918 return native;
919 }
920
921 // CleanUp operations
922
923 private void CleanUpPOD(IntPtr native)
924 {
925 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset));
926
927 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset))
928 {
929 for (var i = 0; i < GetOldFileCount(native); ++i)
930 {
931 Marshal.FreeCoTaskMem(
932 Marshal.ReadIntPtr(
933 Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset),
934 i * Marshal.SizeOf(typeof(IntPtr))));
935 }
936
937 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset));
938 }
939
940 if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset))
941 {
942 for (var i = 0; i < GetOldFileCount(native); ++i)
943 {
944 Marshal.FreeCoTaskMem(
945 Marshal.ReadIntPtr(
946 Marshal.ReadIntPtr(native, interleaveMapArrayOffset),
947 i * Marshal.SizeOf(typeof(IntPtr))));
948 }
949
950 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset));
951 }
952
953 PatchAPIMarshaler.ReleaseMainStruct(native);
954 }
955
956 private void CleanUpPOFI_A(IntPtr native)
957 {
958 for (var i = 0; i < GetOldFileCount(native); ++i)
959 {
960 PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i * patchOldFileInfoSize));
961 }
962
963 PatchAPIMarshaler.ReleaseMainStruct(native);
964 }
965
966 private static void CleanUpPOFI(IntPtr native)
967 {
968 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset))
969 {
970 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset));
971 }
972
973 PatchAPIMarshaler.CleanUpPOFIH(native);
974 }
975
976 private static void CleanUpPOFIH(IntPtr native)
977 {
978 if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset))
979 {
980 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset));
981 }
982
983 if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset))
984 {
985 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset));
986 }
987 }
988 }
989 }
990}
diff --git a/src/wix/WixToolset.Core.Native/ValidationMessage.cs b/src/wix/WixToolset.Core.Native/ValidationMessage.cs
new file mode 100644
index 00000000..d7137326
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/ValidationMessage.cs
@@ -0,0 +1,47 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System.Collections.Generic;
6
7 /// <summary>
8 /// Message from ICE
9 /// </summary>
10 public class ValidationMessage
11 {
12 /// <summary>
13 /// Name of the ICE providing the message.
14 /// </summary>
15 public string IceName { get; set; }
16
17 /// <summary>
18 /// Validation type.
19 /// </summary>
20 public ValidationMessageType Type { get; set; }
21
22 /// <summary>
23 /// Message text.
24 /// </summary>
25 public string Description { get; set; }
26
27 /// <summary>
28 /// Optional help URL for the message.
29 /// </summary>
30 public string HelpUrl { get; set; }
31
32 /// <summary>
33 /// Optional table causing the message.
34 /// </summary>
35 public string Table { get; set; }
36
37 /// <summary>
38 /// Optional column causing the message.
39 /// </summary>
40 public string Column { get; set; }
41
42 /// <summary>
43 /// Optional primary keys causing the message.
44 /// </summary>
45 public IEnumerable<string> PrimaryKeys { get; set; }
46 }
47}
diff --git a/src/wix/WixToolset.Core.Native/ValidationMessageType.cs b/src/wix/WixToolset.Core.Native/ValidationMessageType.cs
new file mode 100644
index 00000000..98635294
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/ValidationMessageType.cs
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 /// <summary>
6 /// Validation message type.
7 /// </summary>
8 public enum ValidationMessageType
9 {
10 /// <summary>
11 /// Failure message reporting the failure of the ICE custom action.
12 /// </summary>
13 InternalFailure = 0,
14
15 /// <summary>
16 /// Error message reporting database authoring that case incorrect behavior.
17 /// </summary>
18 Error = 1,
19
20 /// <summary>
21 /// Warning message reporting database authoring that causes incorrect behavior in certain cases.
22 /// Warnings can also report unexpected side-effects of database authoring.
23 /// </summary>
24 Warning = 2,
25
26 /// <summary>
27 /// Informational message.
28 /// </summary>
29 Info = 3,
30 };
31}
diff --git a/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs
new file mode 100644
index 00000000..9f4b26a3
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/WindowsInstallerValidator.cs
@@ -0,0 +1,427 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.IO;
9 using System.Linq;
10 using System.Threading;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13
14 /// <summary>
15 /// Windows installer validation implementation.
16 /// </summary>
17 public class WindowsInstallerValidator
18 {
19 private const string CubesFolder = "cubes";
20
21 /// <summary>
22 /// Creates a new Windows Installer validator.
23 /// </summary>
24 /// <param name="callback">Callback interface to handle messages.</param>
25 /// <param name="databasePath">Database to validate.</param>
26 /// <param name="cubeFiles">Set of CUBe files to merge.</param>
27 /// <param name="ices">ICEs to execute.</param>
28 /// <param name="suppressedIces">Suppressed ICEs.</param>
29 public WindowsInstallerValidator(IWindowsInstallerValidatorCallback callback, string databasePath, IEnumerable<string> cubeFiles, IEnumerable<string> ices, IEnumerable<string> suppressedIces)
30 {
31 this.Callback = callback;
32 this.DatabasePath = databasePath;
33 this.CubeFiles = cubeFiles;
34 this.Ices = new SortedSet<string>(ices);
35 this.SuppressedIces = new SortedSet<string>(suppressedIces);
36 }
37
38 private IWindowsInstallerValidatorCallback Callback { get; }
39
40 private string DatabasePath { get; }
41
42 private IEnumerable<string> CubeFiles { get; }
43
44 private SortedSet<string> Ices { get; }
45
46 private SortedSet<string> SuppressedIces { get; }
47
48 private bool ValidationSessionInProgress { get; set; }
49
50 private string CurrentIce { get; set; }
51
52 /// <summary>
53 /// Execute the validations.
54 /// </summary>
55 public void Execute()
56 {
57 using (var mutex = new Mutex(false, "WixValidator"))
58 {
59 try
60 {
61 if (!mutex.WaitOne(0))
62 {
63 this.Callback.ValidationBlocked();
64 mutex.WaitOne();
65 }
66 }
67 catch (AbandonedMutexException)
68 {
69 // Another validation process was probably killed, we own the mutex now.
70 }
71
72 try
73 {
74 this.RunValidations();
75 }
76 finally
77 {
78 mutex.ReleaseMutex();
79 }
80 }
81 }
82
83 private void RunValidations()
84 {
85 var previousUILevel = (int)InstallUILevels.Basic;
86 var previousHwnd = IntPtr.Zero;
87 InstallUIHandler previousUIHandler = null;
88
89 try
90 {
91 using (var database = new Database(this.DatabasePath, OpenDatabase.Direct))
92 {
93 var propertyTableExists = database.TableExists("Property");
94 string productCode = null;
95
96 // Remove the product code from the database before opening a session to prevent opening an installed product.
97 if (propertyTableExists)
98 {
99 using (var view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'"))
100 {
101 using (var record = view.Fetch())
102 {
103 if (null != record)
104 {
105 productCode = record.GetString(1);
106
107 using (var dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
108 {
109 }
110 }
111 }
112 }
113 }
114
115 // Merge in the cube databases.
116 foreach (var cubeFile in this.CubeFiles)
117 {
118 var findCubeFile = typeof(WindowsInstallerValidator).Assembly.FindFileRelativeToAssembly(Path.Combine(CubesFolder, cubeFile), searchNativeDllDirectories: false);
119
120 if (!findCubeFile.Found)
121 {
122 throw new WixException(ErrorMessages.CubeFileNotFound(findCubeFile.Path));
123 }
124
125 try
126 {
127 using (var cubeDatabase = new Database(findCubeFile.Path, OpenDatabase.ReadOnly))
128 {
129 try
130 {
131 database.Merge(cubeDatabase, "MergeConflicts");
132 }
133 catch
134 {
135 // ignore merge errors since they are expected in the _Validation table
136 }
137 }
138 }
139 catch (Win32Exception e)
140 {
141 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
142 {
143 throw new WixException(ErrorMessages.CubeFileNotFound(findCubeFile.Path));
144 }
145
146 throw;
147 }
148 }
149
150 // Commit the database before proceeding to ensure the streams don't get confused.
151 database.Commit();
152
153 // The property table may have been added to the database from a cub database without the proper validation rows.
154 if (!propertyTableExists)
155 {
156 using (var view = database.OpenExecuteView("DROP table `Property`"))
157 {
158 }
159 }
160
161 // Get all the action names for ICEs which have not been suppressed.
162 var actions = new List<string>();
163 using (var view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`"))
164 {
165 foreach (var record in view.Records)
166 {
167 var action = record.GetString(1);
168
169 if (!this.SuppressedIces.Contains(action) && this.Ices.Contains(action))
170 {
171 actions.Add(action);
172 }
173 }
174 }
175
176 // Disable the internal UI handler and set an external UI handler.
177 previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd);
178 previousUIHandler = Installer.SetExternalUI(this.ValidationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero);
179
180 // Create a session for running the ICEs.
181 this.ValidationSessionInProgress = true;
182
183 using (var session = new Session(database))
184 {
185 // Add the product code back into the database.
186 if (null != productCode)
187 {
188 // Some CUBs erroneously have a ProductCode property, so delete it if we just picked one up.
189 using (var dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
190 {
191 }
192
193 using (var view = database.OpenExecuteView($"INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{productCode}')"))
194 {
195 }
196 }
197
198 foreach (var action in actions)
199 {
200 this.CurrentIce = action;
201
202 try
203 {
204 session.DoAction(action);
205 }
206 catch (Win32Exception e)
207 {
208 if (!this.Callback.EncounteredError)
209 {
210 throw e;
211 }
212 }
213
214 this.CurrentIce = null;
215 }
216
217 // Mark the validation session complete so we ignore any messages that MSI may fire
218 // during session clean-up.
219 this.ValidationSessionInProgress = false;
220 }
221 }
222 }
223 catch (Win32Exception e)
224 {
225 // Avoid displaying errors twice since one may have already occurred in the UI handler.
226 if (!this.Callback.EncounteredError)
227 {
228 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
229 {
230 // The database path is not passed to this exception since inside wix.exe
231 // this would be the temporary copy and there would be no final output becasue
232 // this error occured; and during standalone validation they should know the path
233 // passed in.
234 throw new WixException(ErrorMessages.ValidationFailedToOpenDatabase());
235 }
236 else if (0x64D == e.NativeErrorCode)
237 {
238 throw new WixException(ErrorMessages.ValidationFailedDueToLowMsiEngine());
239 }
240 else if (0x654 == e.NativeErrorCode)
241 {
242 throw new WixException(ErrorMessages.ValidationFailedDueToInvalidPackage());
243 }
244 else if (0x658 == e.NativeErrorCode)
245 {
246 throw new WixException(ErrorMessages.ValidationFailedDueToMultilanguageMergeModule());
247 }
248 else if (0x659 == e.NativeErrorCode)
249 {
250 throw new WixException(WarningMessages.ValidationFailedDueToSystemPolicy());
251 }
252 else
253 {
254 var msg = String.IsNullOrEmpty(this.CurrentIce) ? e.Message : $"Action - '{this.CurrentIce}' {e.Message}";
255
256 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, msg));
257 }
258 }
259 }
260 finally
261 {
262 this.ValidationSessionInProgress = false;
263
264 Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero);
265 Installer.SetInternalUI(previousUILevel, ref previousHwnd);
266 }
267 }
268
269 /// <summary>
270 /// The validation external UI handler.
271 /// </summary>
272 /// <param name="context">Pointer to an application context.
273 /// This parameter can be used for error checking.</param>
274 /// <param name="messageType">Specifies a combination of one message box style,
275 /// one message box icon type, one default button, and one installation message type.</param>
276 /// <param name="message">Specifies the message text.</param>
277 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
278 private int ValidationUIHandler(IntPtr context, uint messageType, string message)
279 {
280 var continueValidation = true;
281
282 // If we're getting messges during the validation session, log them.
283 // Otherwise, ignore the messages.
284 if (!this.ValidationSessionInProgress)
285 {
286 var parsedMessage = ParseValidationMessage(message, this.CurrentIce);
287
288 continueValidation = this.Callback.ValidationMessage(parsedMessage);
289 }
290
291 return continueValidation ? 1 : 3;
292 }
293
294 /// <summary>
295 /// Parses a message from the Validator.
296 /// </summary>
297 /// <param name="message">A <see cref="String"/> of tab-delmited tokens
298 /// in the validation message.</param>
299 /// <param name="currentIce">The name of the action to which the message
300 /// belongs.</param>
301 /// <exception cref="ArgumentNullException">The message cannot be null.
302 /// </exception>
303 /// <exception cref="WixException">The message does not contain four (4)
304 /// or more tab-delimited tokens.</exception>
305 /// <remarks>
306 /// <para><paramref name="message"/> a tab-delimited set of tokens,
307 /// formatted according to Windows Installer guidelines for ICE
308 /// message. The following table lists what each token by index
309 /// should mean.</para>
310 /// <para><paramref name="currentIce"/> a name that represents the ICE
311 /// action that was executed (e.g. 'ICE08').</para>
312 /// <list type="table">
313 /// <listheader>
314 /// <term>Index</term>
315 /// <description>Description</description>
316 /// </listheader>
317 /// <item>
318 /// <term>0</term>
319 /// <description>Name of the ICE.</description>
320 /// </item>
321 /// <item>
322 /// <term>1</term>
323 /// <description>Message type. See the following list.</description>
324 /// </item>
325 /// <item>
326 /// <term>2</term>
327 /// <description>Detailed description.</description>
328 /// </item>
329 /// <item>
330 /// <term>3</term>
331 /// <description>Help URL or location.</description>
332 /// </item>
333 /// <item>
334 /// <term>4</term>
335 /// <description>Table name.</description>
336 /// </item>
337 /// <item>
338 /// <term>5</term>
339 /// <description>Column name.</description>
340 /// </item>
341 /// <item>
342 /// <term>6</term>
343 /// <description>This and remaining fields are primary keys
344 /// to identify a row.</description>
345 /// </item>
346 /// </list>
347 /// <para>The message types are one of the following value.</para>
348 /// <list type="table">
349 /// <listheader>
350 /// <term>Value</term>
351 /// <description>Message Type</description>
352 /// </listheader>
353 /// <item>
354 /// <term>0</term>
355 /// <description>Failure message reporting the failure of the
356 /// ICE custom action.</description>
357 /// </item>
358 /// <item>
359 /// <term>1</term>
360 /// <description>Error message reporting database authoring that
361 /// case incorrect behavior.</description>
362 /// </item>
363 /// <item>
364 /// <term>2</term>
365 /// <description>Warning message reporting database authoring that
366 /// causes incorrect behavior in certain cases. Warnings can also
367 /// report unexpected side-effects of database authoring.
368 /// </description>
369 /// </item>
370 /// <item>
371 /// <term>3</term>
372 /// <description>Informational message.</description>
373 /// </item>
374 /// </list>
375 /// </remarks>
376 private static ValidationMessage ParseValidationMessage(string message, string currentIce)
377 {
378 if (message == null)
379 {
380 throw new ArgumentNullException(nameof(message));
381 }
382
383 var messageParts = message.Split('\t');
384 if (messageParts.Length < 3)
385 {
386 if (null == currentIce)
387 {
388 throw new WixException(ErrorMessages.UnexpectedExternalUIMessage(message));
389 }
390 else
391 {
392 throw new WixException(ErrorMessages.UnexpectedExternalUIMessage(message, currentIce));
393 }
394 }
395
396 var type = ParseValidationMessageType(messageParts[1]);
397
398 return new ValidationMessage
399 {
400 IceName = messageParts[0],
401 Type = type,
402 Description = messageParts[2],
403 HelpUrl = messageParts.Length > 3 ? messageParts[3] : null,
404 Table = messageParts.Length > 4 ? messageParts[4] : null,
405 Column = messageParts.Length > 5 ? messageParts[4] : null,
406 PrimaryKeys = messageParts.Length > 6 ? messageParts.Skip(6).ToArray() : null
407 };
408 }
409
410 private static ValidationMessageType ParseValidationMessageType(string type)
411 {
412 switch (type)
413 {
414 case "0":
415 return ValidationMessageType.InternalFailure;
416 case "1":
417 return ValidationMessageType.Error;
418 case "2":
419 return ValidationMessageType.Warning;
420 case "3":
421 return ValidationMessageType.Info;
422 default:
423 throw new WixException(ErrorMessages.InvalidValidatorMessageType(type));
424 }
425 }
426 }
427}
diff --git a/src/wix/WixToolset.Core.Native/WixNativeExe.cs b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
new file mode 100644
index 00000000..fb41b2f2
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
@@ -0,0 +1,125 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.IO;
10
11 internal class WixNativeExe
12 {
13 private const string WixNativeExeFileName = "wixnative.exe";
14 private static string PathToWixNativeExe;
15
16 private readonly string commandLine;
17 private readonly List<string> stdinLines = new List<string>();
18
19 public WixNativeExe(params object[] args)
20 {
21 this.commandLine = String.Join(" ", QuoteArgumentsAsNecesary(args));
22 }
23
24 public void AddStdinLine(string line)
25 {
26 this.stdinLines.Add(line);
27 }
28
29 public void AddStdinLines(IEnumerable<string> lines)
30 {
31 this.stdinLines.AddRange(lines);
32 }
33
34 public IEnumerable<string> Run()
35 {
36 EnsurePathToWixNativeExeSet();
37
38 var wixNativeInfo = new ProcessStartInfo(PathToWixNativeExe, this.commandLine)
39 {
40 RedirectStandardInput = true,
41 RedirectStandardOutput = true,
42 CreateNoWindow = true,
43 ErrorDialog = false,
44 UseShellExecute = false
45 };
46
47 var stdoutLines = new List<string>();
48
49 using (var process = Process.Start(wixNativeInfo))
50 {
51 process.OutputDataReceived += (s, a) => stdoutLines.Add(a.Data);
52 process.BeginOutputReadLine();
53
54 if (this.stdinLines.Count > 0)
55 {
56 foreach (var line in this.stdinLines)
57 {
58 process.StandardInput.WriteLine(line);
59 }
60
61 // Trailing blank line indicates stdin complete.
62 process.StandardInput.WriteLine();
63 }
64
65 // If the process successfully exits documentation says we need to wait again
66 // without a timeout to ensure that all of the redirected output is captured.
67 //
68 process.WaitForExit();
69
70 if (process.ExitCode != 0)
71 {
72 throw new Win32Exception(process.ExitCode);
73 }
74 }
75
76 return stdoutLines;
77 }
78
79 private static void EnsurePathToWixNativeExeSet()
80 {
81 if (String.IsNullOrEmpty(PathToWixNativeExe))
82 {
83 var result = typeof(WixNativeExe).Assembly.FindFileRelativeToAssembly(WixNativeExeFileName, searchNativeDllDirectories: true);
84
85 if (!result.Found)
86 {
87 throw new PlatformNotSupportedException(
88 $"Could not find platform specific '{WixNativeExeFileName}'",
89 new FileNotFoundException($"Could not find internal piece of WiX Toolset from: {result.PossiblePaths}", WixNativeExeFileName));
90 }
91
92 PathToWixNativeExe = result.Path;
93 }
94 }
95
96 private static IEnumerable<string> QuoteArgumentsAsNecesary(object[] args)
97 {
98 foreach (var arg in args)
99 {
100 if (arg is string str)
101 {
102 if (String.IsNullOrEmpty(str))
103 {
104 }
105 else if (str.Contains(" ") && !str.StartsWith("\""))
106 {
107 yield return $"\"{str}\"";
108 }
109 else
110 {
111 yield return str;
112 }
113 }
114 else if (arg is int i)
115 {
116 yield return i.ToString();
117 }
118 else
119 {
120 throw new ArgumentException(nameof(args));
121 }
122 }
123 }
124 }
125}
diff --git a/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj
new file mode 100644
index 00000000..fea15922
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj
@@ -0,0 +1,49 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFramework>netstandard2.0</TargetFramework>
8 <DebugType>embedded</DebugType>
9 <Description>WiX Toolset Native Processing</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 <!-- https://github.com/NuGet/Home/issues/10665 -->
12 <NoWarn>NU5128</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <None Include="cubes\darice.cub" CopyToOutputDirectory="PreserveNewest" />
17 <None Include="cubes\mergemod.cub" CopyToOutputDirectory="PreserveNewest" />
18 <None Include="targets\WixToolset.Core.Native.targets" CopyToOutputDirectory="PreserveNewest" />
19 </ItemGroup>
20
21 <ItemGroup Condition=" '$(NCrunch)'=='' ">
22 <ProjectReference Include="..\wixnative\wixnative.vcxproj" ReferenceOutputAssembly="false" PrivateAssets="All" Properties="Platform=ARM64" />
23 <ProjectReference Include="..\wixnative\wixnative.vcxproj" ReferenceOutputAssembly="false" PrivateAssets="All" Properties="Platform=Win32" />
24 <ProjectReference Include="..\wixnative\wixnative.vcxproj" ReferenceOutputAssembly="false" PrivateAssets="All" Properties="Platform=x64" />
25 </ItemGroup>
26
27 <ItemGroup Condition=" '$(NCrunch)'=='' ">
28 <None Include="$(BaseOutputPath)$(Configuration)\x64\mergemod.dll" CopyToOutputDirectory="PreserveNewest" />
29 <None Include="$(BaseOutputPath)$(Configuration)\x64\wixnative.exe" CopyToOutputDirectory="PreserveNewest" />
30 <None Include="$(BaseOutputPath)$(Configuration)\x64\wixnative.pdb" CopyToOutputDirectory="PreserveNewest" />
31 </ItemGroup>
32 <ItemGroup Condition=" '$(NCrunch)'=='1' ">
33 <None Include="$(NCrunchOriginalProjectDir)..\..\build\$(Configuration)\x64\mergemod.dll" CopyToOutputDirectory="PreserveNewest" />
34 <None Include="$(NCrunchOriginalProjectDir)..\..\build\$(Configuration)\x64\wixnative.exe" CopyToOutputDirectory="PreserveNewest" />
35 <None Include="$(NCrunchOriginalProjectDir)..\..\build\$(Configuration)\x64\wixnative.pdb" CopyToOutputDirectory="PreserveNewest" />
36 </ItemGroup>
37
38 <ItemGroup>
39 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
40
41 <!-- Warning: The version for System.IO.FileSystem.AccessControl must be kept in sync with WixToolset.Core.nuspec -->
42 <PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.6.0" />
43 </ItemGroup>
44
45 <ItemGroup>
46 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" />
47 <PackageReference Include="GitInfo" Version="2.1.2" PrivateAssets="all" />
48 </ItemGroup>
49</Project>
diff --git a/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.nuspec b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
new file mode 100644
index 00000000..3091ccd5
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
@@ -0,0 +1,41 @@
1<?xml version="1.0"?>
2<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 <metadata minClientVersion="4.0">
4 <id>$id$</id>
5 <version>$version$</version>
6 <title>$title$</title>
7 <description>$description$</description>
8 <authors>$authors$</authors>
9 <license type="expression">MS-RL</license>
10 <requireLicenseAcceptance>false</requireLicenseAcceptance>
11 <copyright>$copyright$</copyright>
12 <projectUrl>$projectUrl$</projectUrl>
13 <repository type="$repositorytype$" url="$repositoryurl$" commit="$repositorycommit$" />
14 <dependencies>
15 <group targetFramework=".NETStandard2.0">
16 <dependency id="System.IO.FileSystem.AccessControl" version="4.6.0" exclude="Build,Analyzers" />
17 </group>
18 </dependencies>
19 </metadata>
20
21 <files>
22 <file src="netstandard2.0\$id$.dll" target="lib\netstandard2.0" />
23 <file src="netstandard2.0\$id$.xml" target="lib\netstandard2.0" />
24
25 <file src="netstandard2.0\cubes\darice.cub" target="lib\netstandard2.0\cubes" />
26 <file src="netstandard2.0\cubes\mergemod.cub" target="lib\netstandard2.0\cubes" />
27
28 <file src="netstandard2.0\targets\$id$.targets" target="build" />
29 <file src="netstandard2.0\targets\$id$.targets" target="buildTransitive" />
30
31 <file src="ARM64\mergemod.dll" target="runtimes\win-arm64\native" />
32 <file src="ARM64\wixnative.exe" target="runtimes\win-arm64\native" />
33 <file src="ARM64\wixnative.pdb" target="runtimes\win-arm64\native" />
34 <file src="Win32\mergemod.dll" target="runtimes\win-x86\native" />
35 <file src="Win32\wixnative.exe" target="runtimes\win-x86\native" />
36 <file src="Win32\wixnative.pdb" target="runtimes\win-x86\native" />
37 <file src="x64\mergemod.dll" target="runtimes\win-x64\native" />
38 <file src="x64\wixnative.exe" target="runtimes\win-x64\native" />
39 <file src="x64\wixnative.pdb" target="runtimes\win-x64\native" />
40 </files>
41</package>
diff --git a/src/wix/WixToolset.Core.Native/cubes/darice.cub b/src/wix/WixToolset.Core.Native/cubes/darice.cub
new file mode 100644
index 00000000..4292fede
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/cubes/darice.cub
Binary files differ
diff --git a/src/wix/WixToolset.Core.Native/cubes/mergemod.cub b/src/wix/WixToolset.Core.Native/cubes/mergemod.cub
new file mode 100644
index 00000000..def6dd1a
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/cubes/mergemod.cub
Binary files differ
diff --git a/src/wix/WixToolset.Core.Native/targets/WixToolset.Core.Native.targets b/src/wix/WixToolset.Core.Native/targets/WixToolset.Core.Native.targets
new file mode 100644
index 00000000..aafbd405
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/targets/WixToolset.Core.Native.targets
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project>
3 <ItemGroup>
4 <ContentWithTargetPath Include="$(MSBuildThisFileDirectory)\..\lib\netstandard2.0\cubes\**\*.*">
5 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6 <TargetPath>cubes\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
7 </ContentWithTargetPath>
8 </ItemGroup>
9</Project>
diff --git a/src/wix/appveyor-CoreNative.cmd b/src/wix/appveyor-CoreNative.cmd
new file mode 100644
index 00000000..d9691a42
--- /dev/null
+++ b/src/wix/appveyor-CoreNative.cmd
@@ -0,0 +1,19 @@
1@setlocal
2@pushd %~dp0
3@set _C=Release
4@if /i "%1"=="debug" set _C=Debug
5
6:: Restore
7msbuild -p:Configuration=%_C% -t:Restore || exit /b
8
9:: Build
10msbuild -p:Configuration=%_C% src\test\WixToolsetTest.Core.Native\WixToolsetTest.Core.Native.csproj || exit /b
11
12:: Test
13dotnet test -c %_C% --no-build src\test\WixToolsetTest.Core.Native\WixToolsetTest.Core.Native.csproj || exit /b
14
15:: Pack
16msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack src\WixToolset.Core.Native\WixToolset.Core.Native.csproj || exit /b
17
18@popd
19@endlocal
diff --git a/src/wix/appveyor-CoreNative.yml b/src/wix/appveyor-CoreNative.yml
new file mode 100644
index 00000000..364569cf
--- /dev/null
+++ b/src/wix/appveyor-CoreNative.yml
@@ -0,0 +1,44 @@
1# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#
3# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml
4# then update all of the repos.
5
6branches:
7 only:
8 - master
9 - develop
10
11image: Visual Studio 2019
12
13version: 0.0.0.{build}
14configuration: Release
15
16environment:
17 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 NUGET_XMLDOC_MODE: skip
20
21build_script:
22 - appveyor.cmd
23
24test: off
25
26pull_requests:
27 do_not_increment_build_number: true
28
29nuget:
30 disable_publish_on_pr: true
31
32skip_branch_with_pr: true
33skip_tags: true
34
35artifacts:
36- path: build\Release\**\*.nupkg
37 name: nuget
38- path: build\Release\**\*.snupkg
39 name: snupkg
40
41notifications:
42- provider: Slack
43 incoming_webhook:
44 secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA=
diff --git a/src/wix/nuget-CoreNative.config b/src/wix/nuget-CoreNative.config
new file mode 100644
index 00000000..18404582
--- /dev/null
+++ b/src/wix/nuget-CoreNative.config
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8"?>
2<configuration>
3 <packageSources>
4 <clear />
5 <add key="wixtoolset-data" value="https://ci.appveyor.com/nuget/wixtoolset-data" />
6 <add key="wixtoolset-dutil" value="https://ci.appveyor.com/nuget/wixtoolset-dutil" />
7 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
8 </packageSources>
9</configuration> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs b/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs
new file mode 100644
index 00000000..2e43dce4
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs
@@ -0,0 +1,96 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative
4{
5 using System.IO;
6 using System.Linq;
7 using WixToolset.Core.Native;
8 using WixToolsetTest.CoreNative.Utility;
9 using WixToolset.Data;
10 using Xunit;
11
12 public class CabinetFixture
13 {
14 [Fact]
15 public void CanCreateSingleFileCabinet()
16 {
17 using (var fs = new DisposableFileSystem())
18 {
19 var intermediateFolder = fs.GetFolder(true);
20 var cabPath = Path.Combine(intermediateFolder, "testout.cab");
21
22 var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test.txt") };
23
24 var cabinet = new Cabinet(cabPath);
25 cabinet.Compress(files, CompressionLevel.Low);
26
27 Assert.True(File.Exists(cabPath));
28 }
29 }
30
31 [Fact]
32 public void CanEnumerateSingleFileCabinet()
33 {
34 var cabinetPath = TestData.Get(@"TestData\test.cab");
35
36 var cabinet = new Cabinet(cabinetPath);
37 var files = cabinet.Enumerate();
38
39 var file = files.Single();
40 Assert.Equal("test.txt", file.FileId);
41 Assert.Equal(17, file.Size);
42
43 Assert.Equal(19259, file.Date);
44 Assert.Equal(47731, file.Time);
45 // TODO: This doesn't seem to always pass, not clear why but it'd be good to understand one day.
46 // Assert.True(file.SameAsDateTime(new DateTime(2017, 9, 28, 0, 19, 38)));
47 }
48
49 [Fact]
50 public void IntegrationTest()
51 {
52 using (var fs = new DisposableFileSystem())
53 {
54 var intermediateFolder = fs.GetFolder(true);
55 var cabinetPath = Path.Combine(intermediateFolder, "testout.cab");
56 var extractFolder = fs.GetFolder(true);
57
58 // Compress.
59 {
60 var files = new[] {
61 new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test1.txt"),
62 new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test2.txt"),
63 };
64
65 var cabinet = new Cabinet(cabinetPath);
66 cabinet.Compress(files, CompressionLevel.Low);
67 }
68
69 // Extract.
70 {
71 var cabinet = new Cabinet(cabinetPath);
72 var reportedFiles = cabinet.Extract(extractFolder);
73 Assert.Equal(2, reportedFiles.Count());
74 }
75
76 // Enumerate to compare cabinet to extracted files.
77 {
78 var cabinet = new Cabinet(cabinetPath);
79 var enumerated = cabinet.Enumerate().OrderBy(f => f.FileId).ToArray();
80
81 var files = Directory.EnumerateFiles(extractFolder).OrderBy(f => f).ToArray();
82
83 for (var i = 0; i < enumerated.Length; ++i)
84 {
85 var cabFileInfo = enumerated[i];
86 var fileInfo = new FileInfo(files[i]);
87
88 Assert.Equal(cabFileInfo.FileId, fileInfo.Name);
89 Assert.Equal(cabFileInfo.Size, fileInfo.Length);
90 Assert.True(cabFileInfo.SameAsDateTime(fileInfo.CreationTime));
91 }
92 }
93 }
94 }
95 }
96}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/MsmFixture.cs b/src/wix/test/WixToolsetTest.Core.Native/MsmFixture.cs
new file mode 100644
index 00000000..709d4b93
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/MsmFixture.cs
@@ -0,0 +1,17 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative
4{
5 using WixToolset.Core.Native.Msm;
6 using Xunit;
7
8 public class MsmFixture
9 {
10 [Fact]
11 public void CanCreateMsmInterface()
12 {
13 var merge = MsmInterop.GetMsmMerge();
14 Assert.NotNull(merge);
15 }
16 }
17}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
new file mode 100644
index 00000000..ca78f632
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.txt b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.txt
new file mode 100644
index 00000000..cd0db0e1
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.txt
@@ -0,0 +1 @@
This is test.txt. \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Core.Native/Utility/DisposableFileSystem.cs b/src/wix/test/WixToolsetTest.Core.Native/Utility/DisposableFileSystem.cs
new file mode 100644
index 00000000..c9957247
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/Utility/DisposableFileSystem.cs
@@ -0,0 +1,86 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative.Utility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class DisposableFileSystem : IDisposable
10 {
11 protected bool Disposed { get; private set; }
12
13 private List<string> CleanupPaths { get; } = new List<string>();
14
15 public string GetFile(bool create = false)
16 {
17 var path = Path.GetTempFileName();
18
19 if (!create)
20 {
21 File.Delete(path);
22 }
23
24 this.CleanupPaths.Add(path);
25
26 return path;
27 }
28
29 public string GetFolder(bool create = false)
30 {
31 var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
32
33 if (create)
34 {
35 Directory.CreateDirectory(path);
36 }
37
38 this.CleanupPaths.Add(path);
39
40 return path;
41 }
42
43
44 #region // IDisposable
45
46 public void Dispose()
47 {
48 this.Dispose(true);
49 GC.SuppressFinalize(this);
50 }
51
52 protected virtual void Dispose(bool disposing)
53 {
54 if (this.Disposed)
55 {
56 return;
57 }
58
59 if (disposing)
60 {
61 foreach (var path in this.CleanupPaths)
62 {
63 try
64 {
65 if (File.Exists(path))
66 {
67 File.Delete(path);
68 }
69 else if (Directory.Exists(path))
70 {
71 Directory.Delete(path, true);
72 }
73 }
74 catch
75 {
76 // Best effort delete, so ignore any failures.
77 }
78 }
79 }
80
81 this.Disposed = true;
82 }
83
84 #endregion
85 }
86}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/Utility/Pushd.cs b/src/wix/test/WixToolsetTest.Core.Native/Utility/Pushd.cs
new file mode 100644
index 00000000..91700c2f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/Utility/Pushd.cs
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative.Utility
4{
5 using System;
6 using System.IO;
7
8 public class Pushd : IDisposable
9 {
10 protected bool Disposed { get; private set; }
11
12 public Pushd(string path)
13 {
14 this.PreviousDirectory = Directory.GetCurrentDirectory();
15
16 Directory.SetCurrentDirectory(path);
17 }
18
19 public string PreviousDirectory { get; }
20
21 #region // IDisposable
22
23 public void Dispose()
24 {
25 this.Dispose(true);
26 GC.SuppressFinalize(this);
27 }
28
29 protected virtual void Dispose(bool disposing)
30 {
31 if (this.Disposed)
32 {
33 return;
34 }
35
36 if (disposing)
37 {
38 Directory.SetCurrentDirectory(this.PreviousDirectory);
39 }
40
41 this.Disposed = true;
42 }
43
44 #endregion
45 }
46}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/Utility/TestData.cs b/src/wix/test/WixToolsetTest.Core.Native/Utility/TestData.cs
new file mode 100644
index 00000000..cd9c6318
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/Utility/TestData.cs
@@ -0,0 +1,17 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreNative.Utility
4{
5 using System;
6 using System.IO;
7
8 public class TestData
9 {
10 public static string LocalPath => Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
11
12 public static string Get(params string[] paths)
13 {
14 return Path.Combine(LocalPath, Path.Combine(paths));
15 }
16 }
17}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj b/src/wix/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj
new file mode 100644
index 00000000..6068dbea
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 <RuntimeIdentifier>win-x64</RuntimeIdentifier>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\WixToolset.Core.Native\WixToolset.Core.Native.csproj" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <PackageReference Include="GitInfo" Version="2.1.2" />
21
22 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
23 <PackageReference Include="xunit" Version="2.4.1" />
24 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />
25 </ItemGroup>
26</Project>
diff --git a/src/wix/test/version.txt b/src/wix/test/version.txt
new file mode 100644
index 00000000..cf138743
--- /dev/null
+++ b/src/wix/test/version.txt
@@ -0,0 +1 @@
v42.42.{height}-preview.0 \ No newline at end of file
diff --git a/src/wix/wixnative/ARM/mergemod.dll b/src/wix/wixnative/ARM/mergemod.dll
new file mode 100644
index 00000000..739af831
--- /dev/null
+++ b/src/wix/wixnative/ARM/mergemod.dll
Binary files differ
diff --git a/src/wix/wixnative/ARM64/mergemod.dll b/src/wix/wixnative/ARM64/mergemod.dll
new file mode 100644
index 00000000..564a75fc
--- /dev/null
+++ b/src/wix/wixnative/ARM64/mergemod.dll
Binary files differ
diff --git a/src/wix/wixnative/Win32/mergemod.dll b/src/wix/wixnative/Win32/mergemod.dll
new file mode 100644
index 00000000..4286df4d
--- /dev/null
+++ b/src/wix/wixnative/Win32/mergemod.dll
Binary files differ
diff --git a/src/wix/wixnative/enumcab.cpp b/src/wix/wixnative/enumcab.cpp
new file mode 100644
index 00000000..e7717bac
--- /dev/null
+++ b/src/wix/wixnative/enumcab.cpp
@@ -0,0 +1,47 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static INT_PTR __stdcall EnumCallback(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin);
6
7
8HRESULT EnumCabCommand(
9 __in int argc,
10 __in LPWSTR argv[]
11)
12{
13 HRESULT hr = E_INVALIDARG;
14 LPCWSTR wzCabPath = NULL;
15
16 if (argc < 1)
17 {
18 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: cabPath outputFolder");
19 }
20
21 wzCabPath = argv[0];
22
23 hr = CabInitialize(FALSE);
24 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
25
26 hr = CabEnumerate(wzCabPath, L"*", EnumCallback, 0);
27 ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
28
29LExit:
30 CabUninitialize();
31
32 return hr;
33}
34
35
36static INT_PTR __stdcall EnumCallback(
37 __in FDINOTIFICATIONTYPE fdint,
38 __in PFDINOTIFICATION pfdin
39)
40{
41 if (fdint == fdintCOPY_FILE)
42 {
43 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%s\t%d\t%u\t%u", pfdin->psz1, pfdin->cb, pfdin->date, pfdin->time);
44 }
45
46 return 0;
47}
diff --git a/src/wix/wixnative/extractcab.cpp b/src/wix/wixnative/extractcab.cpp
new file mode 100644
index 00000000..53f53266
--- /dev/null
+++ b/src/wix/wixnative/extractcab.cpp
@@ -0,0 +1,50 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static HRESULT ProgressCallback(BOOL fBeginFile, LPCWSTR wzFileId, LPVOID pvContext);
6
7
8HRESULT ExtractCabCommand(
9 __in int argc,
10 __in LPWSTR argv[]
11)
12{
13 HRESULT hr = E_INVALIDARG;
14 LPCWSTR wzCabPath = NULL;
15 LPCWSTR wzOutputFolder = NULL;
16
17 if (argc < 2)
18 {
19 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: cabPath outputFolder");
20 }
21
22 wzCabPath = argv[0];
23 wzOutputFolder = argv[1];
24
25 hr = CabInitialize(FALSE);
26 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
27
28 hr = CabExtract(wzCabPath, L"*", wzOutputFolder, ProgressCallback, NULL, 0);
29 ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
30
31LExit:
32 CabUninitialize();
33
34 return hr;
35}
36
37
38static HRESULT ProgressCallback(
39 __in BOOL fBeginFile,
40 __in LPCWSTR wzFileId,
41 __in LPVOID /*pvContext*/
42)
43{
44 if (fBeginFile)
45 {
46 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls", wzFileId);
47 }
48
49 return S_OK;
50}
diff --git a/src/wix/wixnative/packages.config b/src/wix/wixnative/packages.config
new file mode 100644
index 00000000..a98c0c8e
--- /dev/null
+++ b/src/wix/wixnative/packages.config
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Microsoft.Build.Tasks.Git" version="1.0.0" targetFramework="native" developmentDependency="true" />
4 <package id="Microsoft.SourceLink.Common" version="1.0.0" targetFramework="native" developmentDependency="true" />
5 <package id="Microsoft.SourceLink.GitHub" version="1.0.0" targetFramework="native" developmentDependency="true" />
6 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
7 <package id="WixToolset.DUtil" version="4.0.72" targetFramework="native" />
8</packages> \ No newline at end of file
diff --git a/src/wix/wixnative/precomp.cpp b/src/wix/wixnative/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/wix/wixnative/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
diff --git a/src/wix/wixnative/precomp.h b/src/wix/wixnative/precomp.h
new file mode 100644
index 00000000..5bd617e5
--- /dev/null
+++ b/src/wix/wixnative/precomp.h
@@ -0,0 +1,19 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4#include <windows.h>
5#include <aclapi.h>
6#include <mergemod.h>
7
8#include "dutil.h"
9#include "conutil.h"
10#include "memutil.h"
11#include "pathutil.h"
12#include "strutil.h"
13#include "cabcutil.h"
14#include "cabutil.h"
15
16HRESULT SmartCabCommand(int argc, LPWSTR argv[]);
17HRESULT ResetAclsCommand(int argc, LPWSTR argv[]);
18HRESULT EnumCabCommand(int argc, LPWSTR argv[]);
19HRESULT ExtractCabCommand(int argc, LPWSTR argv[]);
diff --git a/src/wix/wixnative/resetacls.cpp b/src/wix/wixnative/resetacls.cpp
new file mode 100644
index 00000000..8c5bdc56
--- /dev/null
+++ b/src/wix/wixnative/resetacls.cpp
@@ -0,0 +1,51 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5HRESULT ResetAclsCommand(int argc, LPWSTR argv[])
6{
7 Unused(argc);
8 Unused(argv);
9
10 HRESULT hr = S_OK;
11 ACL* pacl = NULL;
12 DWORD cbAcl = sizeof(ACL);
13 LPWSTR sczFilePath = NULL;
14
15 // create an empty (not NULL!) ACL to use on all the files
16 pacl = static_cast<ACL*>(MemAlloc(cbAcl, FALSE));
17 ConsoleExitOnNull(pacl, hr, E_OUTOFMEMORY, CONSOLE_COLOR_RED, "failed to allocate ACL");
18
19#pragma prefast(push)
20#pragma prefast(disable:25029)
21 if (!::InitializeAcl(pacl, cbAcl, ACL_REVISION))
22#pragma prefast(op)
23 {
24 ConsoleExitOnLastError(hr, CONSOLE_COLOR_RED, "failed to initialize ACL");
25 }
26
27 // Reset the existing security permissions on each provided file.
28 for (;;)
29 {
30 hr = ConsoleReadW(&sczFilePath);
31 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to read file path from stdin");
32
33 if (!*sczFilePath)
34 {
35 break;
36 }
37
38 hr = ::SetNamedSecurityInfoW(sczFilePath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pacl, NULL);
39 if (ERROR_FILE_NOT_FOUND != hr && ERROR_PATH_NOT_FOUND != hr)
40 {
41 ConsoleExitOnFailure(hr = HRESULT_FROM_WIN32(hr), CONSOLE_COLOR_RED, "failed to set security descriptor for file: %ls", sczFilePath);
42 }
43 }
44
45 AssertSz(::IsValidAcl(pacl), "ResetAcls() - created invalid ACL");
46
47LExit:
48 ReleaseStr(sczFilePath);
49 ReleaseMem(pacl);
50 return hr;
51}
diff --git a/src/wix/wixnative/smartcab.cpp b/src/wix/wixnative/smartcab.cpp
new file mode 100644
index 00000000..0dff6c94
--- /dev/null
+++ b/src/wix/wixnative/smartcab.cpp
@@ -0,0 +1,160 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static HRESULT CompressFiles(HANDLE hCab);
6static void __stdcall CabNamesCallback(LPWSTR wzFirstCabName, LPWSTR wzNewCabName, LPWSTR wzFileToken);
7
8
9HRESULT SmartCabCommand(
10 __in int argc,
11 __in LPWSTR argv[]
12)
13{
14 HRESULT hr = E_INVALIDARG;
15 LPCWSTR wzCabPath = NULL;
16 LPCWSTR wzCabName = NULL;
17 LPWSTR sczCabDir = NULL;
18 UINT uiFileCount = 0;
19 UINT uiMaxSize = 0;
20 UINT uiMaxThresh = 0;
21 COMPRESSION_TYPE ct = COMPRESSION_TYPE_NONE;
22 HANDLE hCab = NULL;
23
24 if (argc < 1)
25 {
26 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: outCabPath [compressionType] [fileCount] [maxSizePerCabInMB [maxThreshold]]");
27 }
28 else
29 {
30 wzCabPath = argv[0];
31 wzCabName = PathFile(wzCabPath);
32
33 hr = PathGetDirectory(wzCabPath, &sczCabDir);
34 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse directory from path: %ls", wzCabPath);
35
36 if (argc > 1)
37 {
38 UINT uiCompressionType;
39 hr = StrStringToUInt32(argv[1], 0, &uiCompressionType);
40 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse compression type as number: %ls", argv[1]);
41
42 ct = (uiCompressionType > 4) ? COMPRESSION_TYPE_HIGH : static_cast<COMPRESSION_TYPE>(uiCompressionType);
43 }
44
45 if (argc > 2)
46 {
47 hr = StrStringToUInt32(argv[2], 0, &uiFileCount);
48 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse file count as number: %ls", argv[2]);
49 }
50
51 if (argc > 3)
52 {
53 hr = StrStringToUInt32(argv[3], 0, &uiMaxSize);
54 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse max size as number: %ls", argv[3]);
55 }
56
57 if (argc > 4)
58 {
59 hr = StrStringToUInt32(argv[4], 0, &uiMaxThresh);
60 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse max threshold as number: %ls", argv[4]);
61 }
62 }
63
64 hr = CabCBegin(wzCabName, sczCabDir, uiFileCount, uiMaxSize, uiMaxThresh, ct, &hCab);
65 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
66
67 if (uiFileCount > 0)
68 {
69 hr = CompressFiles(hCab);
70 ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
71 }
72
73 hr = CabCFinish(hCab, CabNamesCallback);
74 hCab = NULL; // once finish is called, the handle is invalid.
75 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to compress cabinet: %ls", wzCabPath);
76
77
78LExit:
79 if (hCab)
80 {
81 CabCCancel(hCab);
82 }
83 ReleaseStr(sczCabDir);
84
85 return hr;
86}
87
88
89static HRESULT CompressFiles(
90 __in HANDLE hCab
91)
92{
93 HRESULT hr = S_OK;
94 LPWSTR sczLine = NULL;
95 LPWSTR* rgsczSplit = NULL;
96 UINT cSplit = 0;
97 MSIFILEHASHINFO hashInfo = { sizeof(MSIFILEHASHINFO) };
98
99 for (;;)
100 {
101 hr = ConsoleReadW(&sczLine);
102 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to read smartcab line from stdin");
103
104 if (!*sczLine)
105 {
106 break;
107 }
108
109 hr = StrSplitAllocArray(&rgsczSplit, &cSplit, sczLine, L"\t");
110 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to split smartcab line from stdin: %ls", sczLine);
111
112 if (cSplit != 2 && cSplit != 6)
113 {
114 hr = E_INVALIDARG;
115 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to split smartcab line into hash x 4, token, source file: %ls", sczLine);
116 }
117
118 LPCWSTR wzFilePath = rgsczSplit[0];
119 LPCWSTR wzToken = rgsczSplit[1];
120 PMSIFILEHASHINFO pHashInfo = NULL;
121
122 if (cSplit == 6)
123 {
124 for (int i = 0; i < 4; ++i)
125 {
126 LPCWSTR wzHash = rgsczSplit[i + 2];
127
128 hr = StrStringToInt32(wzHash, 0, reinterpret_cast<INT*>(hashInfo.dwData + i));
129 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to parse hash: %ls for file: %ls", wzHash, wzFilePath);
130 }
131
132 pHashInfo = &hashInfo;
133 }
134
135 hr = CabCAddFile(wzFilePath, wzToken, pHashInfo, hCab);
136 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to add file: %ls", wzFilePath);
137
138 ReleaseNullStrArray(rgsczSplit, cSplit);
139 }
140
141LExit:
142 ReleaseNullStrArray(rgsczSplit, cSplit);
143 ReleaseStr(sczLine);
144
145 return hr;
146}
147
148
149// Callback from PFNFCIGETNEXTCABINET CabCGetNextCabinet method
150// First argument is the name of splitting cabinet without extension e.g. "cab1"
151// Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
152// Third argument is the file token of the first file present in the splitting cabinet
153static void __stdcall CabNamesCallback(
154 __in LPWSTR wzFirstCabName,
155 __in LPWSTR wzNewCabName,
156 __in LPWSTR wzFileToken
157)
158{
159 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls", wzFirstCabName, wzNewCabName, wzFileToken);
160}
diff --git a/src/wix/wixnative/wixnative.cpp b/src/wix/wixnative/wixnative.cpp
new file mode 100644
index 00000000..7bd8dbca
--- /dev/null
+++ b/src/wix/wixnative/wixnative.cpp
@@ -0,0 +1,38 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5int __cdecl wmain(int argc, LPWSTR argv[])
6{
7 HRESULT hr = E_INVALIDARG;
8
9 ConsoleInitialize();
10
11 if (argc < 2)
12 {
13 ConsoleWriteError(hr, CONSOLE_COLOR_RED, "Must specify a command");
14 }
15 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"smartcab", -1))
16 {
17 hr = SmartCabCommand(argc - 2, argv + 2);
18 }
19 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"extractcab", -1))
20 {
21 hr = ExtractCabCommand(argc - 2, argv + 2);
22 }
23 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"enumcab", -1))
24 {
25 hr = EnumCabCommand(argc - 2, argv + 2);
26 }
27 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1))
28 {
29 hr = ResetAclsCommand(argc - 2, argv + 2);
30 }
31 else
32 {
33 ConsoleWriteError(hr, CONSOLE_COLOR_RED, "Unknown command: %ls", argv[1]);
34 }
35
36 ConsoleUninitialize();
37 return HRESULT_CODE(hr);
38}
diff --git a/src/wix/wixnative/wixnative.v3.ncrunchproject b/src/wix/wixnative/wixnative.v3.ncrunchproject
new file mode 100644
index 00000000..319cd523
--- /dev/null
+++ b/src/wix/wixnative/wixnative.v3.ncrunchproject
@@ -0,0 +1,5 @@
1<ProjectConfiguration>
2 <Settings>
3 <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
4 </Settings>
5</ProjectConfiguration> \ No newline at end of file
diff --git a/src/wix/wixnative/wixnative.vcxproj b/src/wix/wixnative/wixnative.vcxproj
new file mode 100644
index 00000000..20959827
--- /dev/null
+++ b/src/wix/wixnative/wixnative.vcxproj
@@ -0,0 +1,76 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup Label="ProjectConfigurations">
6 <ProjectConfiguration Include="Debug|ARM64">
7 <Configuration>Debug</Configuration>
8 <Platform>ARM64</Platform>
9 </ProjectConfiguration>
10 <ProjectConfiguration Include="Debug|Win32">
11 <Configuration>Debug</Configuration>
12 <Platform>Win32</Platform>
13 </ProjectConfiguration>
14 <ProjectConfiguration Include="Debug|x64">
15 <Configuration>Debug</Configuration>
16 <Platform>x64</Platform>
17 </ProjectConfiguration>
18 <ProjectConfiguration Include="Release|ARM64">
19 <Configuration>Release</Configuration>
20 <Platform>ARM64</Platform>
21 </ProjectConfiguration>
22 <ProjectConfiguration Include="Release|Win32">
23 <Configuration>Release</Configuration>
24 <Platform>Win32</Platform>
25 </ProjectConfiguration>
26 <ProjectConfiguration Include="Release|x64">
27 <Configuration>Release</Configuration>
28 <Platform>x64</Platform>
29 </ProjectConfiguration>
30 </ItemGroup>
31
32 <PropertyGroup Label="Globals">
33 <ProjectGuid>{8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}</ProjectGuid>
34 <ConfigurationType>Application</ConfigurationType>
35 <PlatformToolset>v142</PlatformToolset>
36 <CharacterSet>Unicode</CharacterSet>
37 <ProjectSubSystem>Console</ProjectSubSystem>
38 <TargetName>wixnative</TargetName>
39 <Description>WiX Native Processing</Description>
40 <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
41 </PropertyGroup>
42
43 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
44 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
45
46 <PropertyGroup>
47 <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib</ProjectAdditionalLinkLibraries>
48 </PropertyGroup>
49
50 <ItemGroup>
51 <ClCompile Include="wixnative.cpp" />
52 <ClCompile Include="precomp.cpp">
53 <PrecompiledHeader>Create</PrecompiledHeader>
54 </ClCompile>
55 <ClCompile Include="enumcab.cpp" />
56 <ClCompile Include="extractcab.cpp" />
57 <ClCompile Include="resetacls.cpp" />
58 <ClCompile Include="smartcab.cpp" />
59 </ItemGroup>
60
61 <ItemGroup>
62 <ClInclude Include="precomp.h" />
63 </ItemGroup>
64
65 <ItemGroup>
66 <ContentWithTargetPath Include="$(Platform)\mergemod.dll" CopyToOutputDirectory="PreserveNewest" TargetPath="mergemod.dll" />
67 </ItemGroup>
68
69 <ItemGroup>
70 <PackageReference Include="WixToolset.Dutil" Version="4.0.72" />
71 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
72 <PackageReference Include="GitInfo" Version="2.1.2" />
73 </ItemGroup>
74
75 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
76</Project>
diff --git a/src/wix/wixnative/x64/mergemod.dll b/src/wix/wixnative/x64/mergemod.dll
new file mode 100644
index 00000000..b6422174
--- /dev/null
+++ b/src/wix/wixnative/x64/mergemod.dll
Binary files differ