@joebigelow / wix-1 / commits / 3f583916

Merge Dtf

Rob Mensching committed May 11, 2021 at 07:36 UTC 3f583916719eeef598d10a5d4e14ef14f008243b
247 files changed +52895
src/.editorconfig new
+37
@@ -0,0 +1,37 @@
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\.editorconfig
4 +# then update all of the repos.
5 +
6 +root = true
7 +
8 +[*]
9 +charset = utf-8
10 +indent_style = space
11 +indent_size = 4
12 +trim_trailing_whitespace = true
13 +
14 +[*.{cs,vb}]
15 +dotnet_sort_system_directives_first = true
16 +
17 +[*.cs]
18 +csharp_indent_case_contents = true : error
19 +csharp_indent_switch_labels = true : error
20 +csharp_new_line_before_open_brace = all
21 +csharp_prefer_braces = true : error
22 +csharp_style_expression_bodied_methods = when_on_single_line : suggestion
23 +csharp_style_expression_bodied_constructors = when_on_single_line : suggestion
24 +csharp_style_expression_bodied_operators = when_on_single_line : suggestion
25 +csharp_style_expression_bodied_properties = when_on_single_line : suggestion
26 +csharp_style_expression_bodied_indexers = when_on_single_line : suggestion
27 +csharp_style_expression_bodied_accessors = when_on_single_line : suggestion
28 +csharp_style_var_elsewhere = true : suggestion
29 +csharp_style_var_for_built_in_types = true : suggestion
30 +csharp_style_var_when_type_is_apparent = true : suggestion
31 +dotnet_style_qualification_for_event = true : error
32 +dotnet_style_qualification_for_field = true : error
33 +dotnet_style_qualification_for_method = true : error
34 +dotnet_style_qualification_for_property = true : error
35 +
36 +[*.targets]
37 +indent_size = 2
src/dtf/CSharp.Build.props new
+13
@@ -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>
src/dtf/Cpp.Build.props new
+104
@@ -0,0 +1,104 @@
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 + </PropertyGroup>
10 +
11 + <PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'=='' AND '$(VisualStudioVersion)'>='15.0'">
12 + <WindowsTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</WindowsTargetPlatformVersion>
13 + </PropertyGroup>
14 +
15 + <ItemDefinitionGroup>
16 + <ClCompile>
17 + <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
18 + <WarningLevel>Level4</WarningLevel>
19 + <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
20 + <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
21 + <PrecompiledHeader>Use</PrecompiledHeader>
22 + <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
23 + <CallingConvention Condition="'$(Platform)'=='Win32'">StdCall</CallingConvention>
24 + <TreatWarningAsError>true</TreatWarningAsError>
25 + <ExceptionHandling>false</ExceptionHandling>
26 + <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
27 + <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
28 + <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
29 + </ClCompile>
30 + <ResourceCompile>
31 + <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
32 + <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
33 + </ResourceCompile>
34 + <Lib>
35 + <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
36 + </Lib>
37 + <Link>
38 + <SubSystem>$(ProjectSubSystem)</SubSystem>
39 + <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
40 + <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
41 + <GenerateDebugInformation>true</GenerateDebugInformation>
42 + <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
43 + <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
44 + <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
45 + </Link>
46 + </ItemDefinitionGroup>
47 +
48 + <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
49 + <ClCompile>
50 + <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
51 + </ClCompile>
52 + </ItemDefinitionGroup>
53 + <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
54 + <ClCompile>
55 + <CallingConvention>CDecl</CallingConvention>
56 + </ClCompile>
57 + </ItemDefinitionGroup>
58 + <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
59 + <ClCompile>
60 + <DebugInformationFormat>OldStyle</DebugInformationFormat>
61 + <OmitDefaultLibName>true</OmitDefaultLibName>
62 + <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
63 + </ClCompile>
64 + </ItemDefinitionGroup>
65 + <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
66 + <ClCompile>
67 + <Optimization>Disabled</Optimization>
68 + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
69 + <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
70 + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
71 + </ClCompile>
72 + </ItemDefinitionGroup>
73 + <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
74 + <ClCompile>
75 + <BasicRuntimeChecks></BasicRuntimeChecks>
76 + <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
77 + </ClCompile>
78 + </ItemDefinitionGroup>
79 + <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
80 + <ClCompile>
81 + <Optimization>MinSpace</Optimization>
82 + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
83 + <FunctionLevelLinking>true</FunctionLevelLinking>
84 + <IntrinsicFunctions>true</IntrinsicFunctions>
85 + <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
86 + </ClCompile>
87 + <Link>
88 + <EnableCOMDATFolding>true</EnableCOMDATFolding>
89 + <OptimizeReferences>true</OptimizeReferences>
90 + </Link>
91 + </ItemDefinitionGroup>
92 + <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
93 + <ClCompile>
94 + <BasicRuntimeChecks></BasicRuntimeChecks>
95 + <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
96 + </ClCompile>
97 + </ItemDefinitionGroup>
98 + <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
99 + <Link>
100 + <KeyFile>$(LinkKeyFile)</KeyFile>
101 + <DelaySign>$(LinkDelaySign)</DelaySign>
102 + </Link>
103 + </ItemDefinitionGroup>
104 +</Project>
src/dtf/Custom.Build.props new
+20
@@ -0,0 +1,20 @@
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 + <DebugType>embedded</DebugType>
7 + <PublishRepositoryUrl>true</PublishRepositoryUrl>
8 + </PropertyGroup>
9 +
10 + <PropertyGroup Condition=" '$(UsingMicrosoftNETSdk)'!='true' and '$(Configuration)' == 'Debug' ">
11 + <DebugSymbols>true</DebugSymbols>
12 + <Optimize>false</Optimize>
13 + <DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
14 + </PropertyGroup>
15 + <PropertyGroup Condition=" '$(UsingMicrosoftNETSdk)'!='true' and '$(Configuration)' == 'Release' ">
16 + <DebugSymbols>false</DebugSymbols>
17 + <Optimize>true</Optimize>
18 + <DefineConstants>$(DefineConstants);TRACE</DefineConstants>
19 + </PropertyGroup>
20 +</Project>
src/dtf/Directory.Build.props new
+29
@@ -0,0 +1,29 @@
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 + <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages>
12 +
13 + <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
14 + <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
15 + <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
16 + <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
17 +
18 + <Authors>WiX Toolset Team</Authors>
19 + <Company>WiX Toolset</Company>
20 + <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
21 + <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
22 + <Product>WiX Toolset</Product>
23 + </PropertyGroup>
24 +
25 + <Import Project="CSharp.Build.props" Condition=" '$(MSBuildProjectExtension)'=='.csproj' and Exists('CSharp.Build.props') " />
26 + <Import Project="Cpp.Build.props" Condition=" Exists('Cpp.Build.props') And '$(MSBuildProjectExtension)'=='.vcxproj' " />
27 + <Import Project="Wix.Build.props" Condition=" Exists('Wix.Build.props') And '$(MSBuildProjectExtension)'=='.wixproj' " />
28 + <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
29 +</Project>
src/dtf/Directory.Build.targets new
+56
@@ -0,0 +1,56 @@
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 +<!--
8 + Replace PackageReferences with ProjectReferences when the projects can be found in .sln.
9 + See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284
10 +-->
11 +<Project>
12 + <PropertyGroup>
13 + <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
14 + <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
15 + </PropertyGroup>
16 +
17 + <PropertyGroup>
18 + <ReplacePackageReferences>true</ReplacePackageReferences>
19 + <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath>
20 + <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath>
21 + </PropertyGroup>
22 +
23 + <Choose>
24 + <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')">
25 +
26 + <PropertyGroup>
27 + <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent>
28 + <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir>
29 + <RegexPattern>(?&lt;="[PackageName]", ")(.*)(?=", ")</RegexPattern>
30 + </PropertyGroup>
31 +
32 + <ItemGroup>
33 + <!-- Keep the identity of the PackageReference -->
34 + <SmartPackageReference Include="@(PackageReference)">
35 + <PackageName>%(Identity)</PackageName>
36 + <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
37 + </SmartPackageReference>
38 +
39 + <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function -->
40 + <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))">
41 + <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
42 + <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath>
43 + </PackageInSolution>
44 +
45 + <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/>
46 +
47 + <!-- Remove the package references that are now referenced as projects -->
48 + <PackageReference Remove="@(PackageInSolution->'%(PackageName)' )"/>
49 + </ItemGroup>
50 +
51 + </When>
52 + </Choose>
53 +
54 + <Import Project="Wix.Build.targets" Condition=" Exists('Wix.Build.targets') And '$(MSBuildProjectExtension)'=='.wixproj' " />
55 + <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
56 +</Project>
src/dtf/README.md new
+2
@@ -0,0 +1,2 @@
1 +# Dtf
2 +WixToolset.Dtf - managed libraries for the Windows Installer
src/dtf/WixToolset.Dtf.Compression.Cab/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Diagnostics.CodeAnalysis;
4 +
5 +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Cab")]
src/dtf/WixToolset.Dtf.Compression.Cab/CabEngine.cs new
+164
@@ -0,0 +1,164 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 +
9 + /// <summary>
10 + /// Engine capable of packing and unpacking archives in the cabinet format.
11 + /// </summary>
12 + public class CabEngine : CompressionEngine
13 + {
14 + private CabPacker packer;
15 + private CabUnpacker unpacker;
16 +
17 + /// <summary>
18 + /// Creates a new instance of the cabinet engine.
19 + /// </summary>
20 + public CabEngine()
21 + : base()
22 + {
23 + }
24 +
25 + /// <summary>
26 + /// Disposes of resources allocated by the cabinet engine.
27 + /// </summary>
28 + /// <param name="disposing">If true, the method has been called directly
29 + /// or indirectly by a user's code, so managed and unmanaged resources
30 + /// will be disposed. If false, the method has been called by the runtime
31 + /// from inside the finalizer, and only unmanaged resources will be
32 + /// disposed.</param>
33 + protected override void Dispose(bool disposing)
34 + {
35 + if (disposing)
36 + {
37 + if (packer != null)
38 + {
39 + packer.Dispose();
40 + packer = null;
41 + }
42 + if (unpacker != null)
43 + {
44 + unpacker.Dispose();
45 + unpacker = null;
46 + }
47 + }
48 +
49 + base.Dispose(disposing);
50 + }
51 +
52 + private CabPacker Packer
53 + {
54 + get
55 + {
56 + if (this.packer == null)
57 + {
58 + this.packer = new CabPacker(this);
59 + }
60 +
61 + return this.packer;
62 + }
63 + }
64 +
65 + private CabUnpacker Unpacker
66 + {
67 + get
68 + {
69 + if (this.unpacker == null)
70 + {
71 + this.unpacker = new CabUnpacker(this);
72 + }
73 +
74 + return this.unpacker;
75 + }
76 + }
77 +
78 + /// <summary>
79 + /// Creates a cabinet or chain of cabinets.
80 + /// </summary>
81 + /// <param name="streamContext">A context interface to handle opening
82 + /// and closing of cabinet and file streams.</param>
83 + /// <param name="files">The paths of the files in the archive (not
84 + /// external file paths).</param>
85 + /// <param name="maxArchiveSize">The maximum number of bytes for one
86 + /// cabinet before the contents are chained to the next cabinet, or zero
87 + /// for unlimited cabinet size.</param>
88 + /// <exception cref="ArchiveException">The cabinet could not be
89 + /// created.</exception>
90 + /// <remarks>
91 + /// The stream context implementation may provide a mapping from the
92 + /// file paths within the cabinet to the external file paths.
93 + /// <para>Smaller folder sizes can make it more efficient to extract
94 + /// individual files out of large cabinet packages.</para>
95 + /// </remarks>
96 + public override void Pack(
97 + IPackStreamContext streamContext,
98 + IEnumerable<string> files,
99 + long maxArchiveSize)
100 + {
101 + this.Packer.CompressionLevel = this.CompressionLevel;
102 + this.Packer.UseTempFiles = this.UseTempFiles;
103 + this.Packer.Pack(streamContext, files, maxArchiveSize);
104 + }
105 +
106 + /// <summary>
107 + /// Checks whether a Stream begins with a header that indicates
108 + /// it is a valid cabinet file.
109 + /// </summary>
110 + /// <param name="stream">Stream for reading the cabinet file.</param>
111 + /// <returns>True if the stream is a valid cabinet file
112 + /// (with no offset); false otherwise.</returns>
113 + public override bool IsArchive(Stream stream)
114 + {
115 + return this.Unpacker.IsArchive(stream);
116 + }
117 +
118 + /// <summary>
119 + /// Gets information about files in a cabinet or cabinet chain.
120 + /// </summary>
121 + /// <param name="streamContext">A context interface to handle opening
122 + /// and closing of cabinet and file streams.</param>
123 + /// <param name="fileFilter">A predicate that can determine
124 + /// which files to process, optional.</param>
125 + /// <returns>Information about files in the cabinet stream.</returns>
126 + /// <exception cref="ArchiveException">The cabinet provided
127 + /// by the stream context is not valid.</exception>
128 + /// <remarks>
129 + /// The <paramref name="fileFilter"/> predicate takes an internal file
130 + /// path and returns true to include the file or false to exclude it.
131 + /// </remarks>
132 + public override IList<ArchiveFileInfo> GetFileInfo(
133 + IUnpackStreamContext streamContext,
134 + Predicate<string> fileFilter)
135 + {
136 + return this.Unpacker.GetFileInfo(streamContext, fileFilter);
137 + }
138 +
139 + /// <summary>
140 + /// Extracts files from a cabinet or cabinet chain.
141 + /// </summary>
142 + /// <param name="streamContext">A context interface to handle opening
143 + /// and closing of cabinet and file streams.</param>
144 + /// <param name="fileFilter">An optional predicate that can determine
145 + /// which files to process.</param>
146 + /// <exception cref="ArchiveException">The cabinet provided
147 + /// by the stream context is not valid.</exception>
148 + /// <remarks>
149 + /// The <paramref name="fileFilter"/> predicate takes an internal file
150 + /// path and returns true to include the file or false to exclude it.
151 + /// </remarks>
152 + public override void Unpack(
153 + IUnpackStreamContext streamContext,
154 + Predicate<string> fileFilter)
155 + {
156 + this.Unpacker.Unpack(streamContext, fileFilter);
157 + }
158 +
159 + internal void ReportProgress(ArchiveProgressEventArgs e)
160 + {
161 + base.OnProgress(e);
162 + }
163 + }
164 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabException.cs new
+154
@@ -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 +
3 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Resources;
8 + using System.Globalization;
9 + using System.Runtime.Serialization;
10 +
11 + /// <summary>
12 + /// Exception class for cabinet operations.
13 + /// </summary>
14 + [Serializable]
15 + public class CabException : ArchiveException
16 + {
17 + private static ResourceManager errorResources;
18 + private int error;
19 + private int errorCode;
20 +
21 + /// <summary>
22 + /// Creates a new CabException with a specified error message and a reference to the
23 + /// inner exception that is the cause of this exception.
24 + /// </summary>
25 + /// <param name="message">The message that describes the error.</param>
26 + /// <param name="innerException">The exception that is the cause of the current exception. If the
27 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
28 + /// is raised in a catch block that handles the inner exception.</param>
29 + public CabException(string message, Exception innerException)
30 + : this(0, 0, message, innerException) { }
31 +
32 + /// <summary>
33 + /// Creates a new CabException with a specified error message.
34 + /// </summary>
35 + /// <param name="message">The message that describes the error.</param>
36 + public CabException(string message)
37 + : this(0, 0, message, null) { }
38 +
39 + /// <summary>
40 + /// Creates a new CabException.
41 + /// </summary>
42 + public CabException()
43 + : this(0, 0, null, null) { }
44 +
45 + internal CabException(int error, int errorCode, string message, Exception innerException)
46 + : base(message, innerException)
47 + {
48 + this.error = error;
49 + this.errorCode = errorCode;
50 + }
51 +
52 + internal CabException(int error, int errorCode, string message)
53 + : this(error, errorCode, message, null) { }
54 +
55 + /// <summary>
56 + /// Initializes a new instance of the CabException class with serialized data.
57 + /// </summary>
58 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
59 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
60 + protected CabException(SerializationInfo info, StreamingContext context) : base(info, context)
61 + {
62 + if (info == null)
63 + {
64 + throw new ArgumentNullException("info");
65 + }
66 +
67 + this.error = info.GetInt32("cabError");
68 + this.errorCode = info.GetInt32("cabErrorCode");
69 + }
70 +
71 + /// <summary>
72 + /// Gets the FCI or FDI cabinet engine error number.
73 + /// </summary>
74 + /// <value>A cabinet engine error number, or 0 if the exception was
75 + /// not related to a cabinet engine error number.</value>
76 + public int Error
77 + {
78 + get
79 + {
80 + return this.error;
81 + }
82 + }
83 +
84 + /// <summary>
85 + /// Gets the Win32 error code.
86 + /// </summary>
87 + /// <value>A Win32 error code, or 0 if the exception was
88 + /// not related to a Win32 error.</value>
89 + public int ErrorCode
90 + {
91 + get
92 + {
93 + return this.errorCode;
94 + }
95 + }
96 +
97 + internal static ResourceManager ErrorResources
98 + {
99 + get
100 + {
101 + if (errorResources == null)
102 + {
103 + errorResources = new ResourceManager(
104 + typeof(CabException).Namespace + ".Errors",
105 + typeof(CabException).Assembly);
106 + }
107 + return errorResources;
108 + }
109 + }
110 +
111 + /// <summary>
112 + /// Sets the SerializationInfo with information about the exception.
113 + /// </summary>
114 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 + {
118 + if (info == null)
119 + {
120 + throw new ArgumentNullException("info");
121 + }
122 +
123 + info.AddValue("cabError", this.error);
124 + info.AddValue("cabErrorCode", this.errorCode);
125 + base.GetObjectData(info, context);
126 + }
127 +
128 + internal static string GetErrorMessage(int error, int errorCode, bool extracting)
129 + {
130 + const int FCI_ERROR_RESOURCE_OFFSET = 1000;
131 + const int FDI_ERROR_RESOURCE_OFFSET = 2000;
132 + int resourceOffset = (extracting ? FDI_ERROR_RESOURCE_OFFSET : FCI_ERROR_RESOURCE_OFFSET);
133 +
134 + string msg = CabException.ErrorResources.GetString(
135 + (resourceOffset + error).ToString(CultureInfo.InvariantCulture.NumberFormat),
136 + CultureInfo.CurrentCulture);
137 +
138 + if (msg == null)
139 + {
140 + msg = CabException.ErrorResources.GetString(
141 + resourceOffset.ToString(CultureInfo.InvariantCulture.NumberFormat),
142 + CultureInfo.CurrentCulture);
143 + }
144 +
145 + if (errorCode != 0)
146 + {
147 + const string GENERIC_ERROR_RESOURCE = "1";
148 + string msg2 = CabException.ErrorResources.GetString(GENERIC_ERROR_RESOURCE, CultureInfo.CurrentCulture);
149 + msg = String.Format(CultureInfo.InvariantCulture, "{0} " + msg2, msg, errorCode);
150 + }
151 + return msg;
152 + }
153 + }
154 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabFileInfo.cs new
+141
@@ -0,0 +1,141 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Runtime.Serialization;
8 +
9 + /// <summary>
10 + /// Object representing a compressed file within a cabinet package; provides operations for getting
11 + /// the file properties and extracting the file.
12 + /// </summary>
13 + [Serializable]
14 + public class CabFileInfo : ArchiveFileInfo
15 + {
16 + private int cabFolder;
17 +
18 + /// <summary>
19 + /// Creates a new CabinetFileInfo object representing a file within a cabinet in a specified path.
20 + /// </summary>
21 + /// <param name="cabinetInfo">An object representing the cabinet containing the file.</param>
22 + /// <param name="filePath">The path to the file within the cabinet. Usually, this is a simple file
23 + /// name, but if the cabinet contains a directory structure this may include the directory.</param>
24 + public CabFileInfo(CabInfo cabinetInfo, string filePath)
25 + : base(cabinetInfo, filePath)
26 + {
27 + if (cabinetInfo == null)
28 + {
29 + throw new ArgumentNullException("cabinetInfo");
30 + }
31 +
32 + this.cabFolder = -1;
33 + }
34 +
35 + /// <summary>
36 + /// Creates a new CabinetFileInfo object with all parameters specified,
37 + /// used internally when reading the metadata out of a cab.
38 + /// </summary>
39 + /// <param name="filePath">The internal path and name of the file in the cab.</param>
40 + /// <param name="cabFolder">The folder number containing the file.</param>
41 + /// <param name="cabNumber">The cabinet number where the file starts.</param>
42 + /// <param name="attributes">The stored attributes of the file.</param>
43 + /// <param name="lastWriteTime">The stored last write time of the file.</param>
44 + /// <param name="length">The uncompressed size of the file.</param>
45 + internal CabFileInfo(
46 + string filePath,
47 + int cabFolder,
48 + int cabNumber,
49 + FileAttributes attributes,
50 + DateTime lastWriteTime,
51 + long length)
52 + : base(filePath, cabNumber, attributes, lastWriteTime, length)
53 + {
54 + this.cabFolder = cabFolder;
55 + }
56 +
57 + /// <summary>
58 + /// Initializes a new instance of the CabinetFileInfo class with serialized data.
59 + /// </summary>
60 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
61 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
62 + protected CabFileInfo(SerializationInfo info, StreamingContext context)
63 + : base(info, context)
64 + {
65 + this.cabFolder = info.GetInt32("cabFolder");
66 + }
67 +
68 + /// <summary>
69 + /// Sets the SerializationInfo with information about the archive.
70 + /// </summary>
71 + /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
72 + /// <param name="context">The StreamingContext that contains contextual information
73 + /// about the source or destination.</param>
74 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
75 + {
76 + base.GetObjectData(info, context);
77 + info.AddValue("cabFolder", this.cabFolder);
78 + }
79 +
80 + /// <summary>
81 + /// Gets or sets the cabinet that contains this file.
82 + /// </summary>
83 + /// <value>
84 + /// The CabinetInfo instance that retrieved this file information -- this
85 + /// may be null if the CabinetFileInfo object was returned directly from a
86 + /// stream.
87 + /// </value>
88 + public CabInfo Cabinet
89 + {
90 + get
91 + {
92 + return (CabInfo) this.Archive;
93 + }
94 + }
95 +
96 + /// <summary>
97 + /// Gets the full path of the cabinet that contains this file.
98 + /// </summary>
99 + /// <value>The full path of the cabinet that contains this file.</value>
100 + public string CabinetName
101 + {
102 + get
103 + {
104 + return this.ArchiveName;
105 + }
106 + }
107 +
108 + /// <summary>
109 + /// Gets the number of the folder containing this file.
110 + /// </summary>
111 + /// <value>The number of the cabinet folder containing this file.</value>
112 + /// <remarks>A single folder or the first folder of a cabinet
113 + /// (or chain of cabinets) is numbered 0.</remarks>
114 + public int CabinetFolderNumber
115 + {
116 + get
117 + {
118 + if (this.cabFolder < 0)
119 + {
120 + this.Refresh();
121 + }
122 + return this.cabFolder;
123 + }
124 + }
125 +
126 + /// <summary>
127 + /// Refreshes the information in this object with new data retrieved
128 + /// from an archive.
129 + /// </summary>
130 + /// <param name="newFileInfo">Fresh instance for the same file just
131 + /// read from the archive.</param>
132 + /// <remarks>
133 + /// This implementation refreshes the <see cref="CabinetFolderNumber"/>.
134 + /// </remarks>
135 + protected override void Refresh(ArchiveFileInfo newFileInfo)
136 + {
137 + base.Refresh(newFileInfo);
138 + this.cabFolder = ((CabFileInfo) newFileInfo).cabFolder;
139 + }
140 + }
141 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabInfo.cs new
+82
@@ -0,0 +1,82 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Runtime.Serialization;
8 +
9 + /// <summary>
10 + /// Object representing a cabinet file on disk; provides access to
11 + /// file-based operations on the cabinet file.
12 + /// </summary>
13 + /// <remarks>
14 + /// Generally, the methods on this class are much easier to use than the
15 + /// stream-based interfaces provided by the <see cref="CabEngine"/> class.
16 + /// </remarks>
17 + [Serializable]
18 + public class CabInfo : ArchiveInfo
19 + {
20 + /// <summary>
21 + /// Creates a new CabinetInfo object representing a cabinet file in a specified path.
22 + /// </summary>
23 + /// <param name="path">The path to the cabinet file. When creating a cabinet file, this file does not
24 + /// necessarily exist yet.</param>
25 + public CabInfo(string path)
26 + : base(path)
27 + {
28 + }
29 +
30 + /// <summary>
31 + /// Initializes a new instance of the CabinetInfo class with serialized data.
32 + /// </summary>
33 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 + protected CabInfo(SerializationInfo info, StreamingContext context)
36 + : base(info, context)
37 + {
38 + }
39 +
40 + /// <summary>
41 + /// Creates a compression engine that does the low-level work for
42 + /// this object.
43 + /// </summary>
44 + /// <returns>A new <see cref="CabEngine"/> instance.</returns>
45 + /// <remarks>
46 + /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 + /// immediately after use.
48 + /// </remarks>
49 + protected override CompressionEngine CreateCompressionEngine()
50 + {
51 + return new CabEngine();
52 + }
53 +
54 + /// <summary>
55 + /// Gets information about the files contained in the archive.
56 + /// </summary>
57 + /// <returns>A list of <see cref="CabFileInfo"/> objects, each
58 + /// containing information about a file in the archive.</returns>
59 + public new IList<CabFileInfo> GetFiles()
60 + {
61 + IList<ArchiveFileInfo> files = base.GetFiles();
62 + List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
63 + foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
64 + return cabFiles.AsReadOnly();
65 + }
66 +
67 + /// <summary>
68 + /// Gets information about the certain files contained in the archive file.
69 + /// </summary>
70 + /// <param name="searchPattern">The search string, such as
71 + /// &quot;*.txt&quot;.</param>
72 + /// <returns>A list of <see cref="CabFileInfo"/> objects, each containing
73 + /// information about a file in the archive.</returns>
74 + public new IList<CabFileInfo> GetFiles(string searchPattern)
75 + {
76 + IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 + List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
78 + foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
79 + return cabFiles.AsReadOnly();
80 + }
81 + }
82 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs new
+653
@@ -0,0 +1,653 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using System.Globalization;
10 + using System.Runtime.InteropServices;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + internal class CabPacker : CabWorker
14 + {
15 + private const string TempStreamName = "%%TEMP%%";
16 +
17 + private NativeMethods.FCI.Handle fciHandle;
18 +
19 + // These delegates need to be saved as member variables
20 + // so that they don't get GC'd.
21 + private NativeMethods.FCI.PFNALLOC fciAllocMemHandler;
22 + private NativeMethods.FCI.PFNFREE fciFreeMemHandler;
23 + private NativeMethods.FCI.PFNOPEN fciOpenStreamHandler;
24 + private NativeMethods.FCI.PFNREAD fciReadStreamHandler;
25 + private NativeMethods.FCI.PFNWRITE fciWriteStreamHandler;
26 + private NativeMethods.FCI.PFNCLOSE fciCloseStreamHandler;
27 + private NativeMethods.FCI.PFNSEEK fciSeekStreamHandler;
28 + private NativeMethods.FCI.PFNFILEPLACED fciFilePlacedHandler;
29 + private NativeMethods.FCI.PFNDELETE fciDeleteFileHandler;
30 + private NativeMethods.FCI.PFNGETTEMPFILE fciGetTempFileHandler;
31 +
32 + private NativeMethods.FCI.PFNGETNEXTCABINET fciGetNextCabinet;
33 + private NativeMethods.FCI.PFNSTATUS fciCreateStatus;
34 + private NativeMethods.FCI.PFNGETOPENINFO fciGetOpenInfo;
35 +
36 + private IPackStreamContext context;
37 +
38 + private FileAttributes fileAttributes;
39 + private DateTime fileLastWriteTime;
40 +
41 + private int maxCabBytes;
42 +
43 + private long totalFolderBytesProcessedInCurrentCab;
44 +
45 + private CompressionLevel compressionLevel;
46 + private bool dontUseTempFiles;
47 + private IList<Stream> tempStreams;
48 +
49 + public CabPacker(CabEngine cabEngine)
50 + : base(cabEngine)
51 + {
52 + this.fciAllocMemHandler = this.CabAllocMem;
53 + this.fciFreeMemHandler = this.CabFreeMem;
54 + this.fciOpenStreamHandler = this.CabOpenStreamEx;
55 + this.fciReadStreamHandler = this.CabReadStreamEx;
56 + this.fciWriteStreamHandler = this.CabWriteStreamEx;
57 + this.fciCloseStreamHandler = this.CabCloseStreamEx;
58 + this.fciSeekStreamHandler = this.CabSeekStreamEx;
59 + this.fciFilePlacedHandler = this.CabFilePlaced;
60 + this.fciDeleteFileHandler = this.CabDeleteFile;
61 + this.fciGetTempFileHandler = this.CabGetTempFile;
62 + this.fciGetNextCabinet = this.CabGetNextCabinet;
63 + this.fciCreateStatus = this.CabCreateStatus;
64 + this.fciGetOpenInfo = this.CabGetOpenInfo;
65 + this.tempStreams = new List<Stream>();
66 + this.compressionLevel = CompressionLevel.Normal;
67 + }
68 +
69 + public bool UseTempFiles
70 + {
71 + get
72 + {
73 + return !this.dontUseTempFiles;
74 + }
75 +
76 + set
77 + {
78 + this.dontUseTempFiles = !value;
79 + }
80 + }
81 +
82 + public CompressionLevel CompressionLevel
83 + {
84 + get
85 + {
86 + return this.compressionLevel;
87 + }
88 +
89 + set
90 + {
91 + this.compressionLevel = value;
92 + }
93 + }
94 +
95 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
96 + private void CreateFci(long maxArchiveSize)
97 + {
98 + NativeMethods.FCI.CCAB ccab = new NativeMethods.FCI.CCAB();
99 + if (maxArchiveSize > 0 && maxArchiveSize < ccab.cb)
100 + {
101 + ccab.cb = Math.Max(
102 + NativeMethods.FCI.MIN_DISK, (int) maxArchiveSize);
103 + }
104 +
105 + object maxFolderSizeOption = this.context.GetOption(
106 + "maxFolderSize", null);
107 + if (maxFolderSizeOption != null)
108 + {
109 + long maxFolderSize = Convert.ToInt64(
110 + maxFolderSizeOption, CultureInfo.InvariantCulture);
111 + if (maxFolderSize > 0 && maxFolderSize < ccab.cbFolderThresh)
112 + {
113 + ccab.cbFolderThresh = (int) maxFolderSize;
114 + }
115 + }
116 +
117 + this.maxCabBytes = ccab.cb;
118 + ccab.szCab = this.context.GetArchiveName(0);
119 + if (ccab.szCab == null)
120 + {
121 + throw new FileNotFoundException(
122 + "Cabinet name not provided by stream context.");
123 + }
124 + ccab.setID = (short) new Random().Next(
125 + Int16.MinValue, Int16.MaxValue + 1);
126 + this.CabNumbers[ccab.szCab] = 0;
127 + this.currentArchiveName = ccab.szCab;
128 + this.totalArchives = 1;
129 + this.CabStream = null;
130 +
131 + this.Erf.Clear();
132 + this.fciHandle = NativeMethods.FCI.Create(
133 + this.ErfHandle.AddrOfPinnedObject(),
134 + this.fciFilePlacedHandler,
135 + this.fciAllocMemHandler,
136 + this.fciFreeMemHandler,
137 + this.fciOpenStreamHandler,
138 + this.fciReadStreamHandler,
139 + this.fciWriteStreamHandler,
140 + this.fciCloseStreamHandler,
141 + this.fciSeekStreamHandler,
142 + this.fciDeleteFileHandler,
143 + this.fciGetTempFileHandler,
144 + ccab,
145 + IntPtr.Zero);
146 + this.CheckError(false);
147 + }
148 +
149 + public void Pack(
150 + IPackStreamContext streamContext,
151 + IEnumerable<string> files,
152 + long maxArchiveSize)
153 + {
154 + if (streamContext == null)
155 + {
156 + throw new ArgumentNullException("streamContext");
157 + }
158 +
159 + if (files == null)
160 + {
161 + throw new ArgumentNullException("files");
162 + }
163 +
164 + lock (this)
165 + {
166 + try
167 + {
168 + this.context = streamContext;
169 +
170 + this.ResetProgressData();
171 +
172 + this.CreateFci(maxArchiveSize);
173 +
174 + foreach (string file in files)
175 + {
176 + FileAttributes attributes;
177 + DateTime lastWriteTime;
178 + Stream fileStream = this.context.OpenFileReadStream(
179 + file,
180 + out attributes,
181 + out lastWriteTime);
182 + if (fileStream != null)
183 + {
184 + this.totalFileBytes += fileStream.Length;
185 + this.totalFiles++;
186 + this.context.CloseFileReadStream(file, fileStream);
187 + }
188 + }
189 +
190 + long uncompressedBytesInFolder = 0;
191 + this.currentFileNumber = -1;
192 +
193 + foreach (string file in files)
194 + {
195 + FileAttributes attributes;
196 + DateTime lastWriteTime;
197 + Stream fileStream = this.context.OpenFileReadStream(
198 + file, out attributes, out lastWriteTime);
199 + if (fileStream == null)
200 + {
201 + continue;
202 + }
203 +
204 + if (fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER)
205 + {
206 + throw new NotSupportedException(String.Format(
207 + CultureInfo.InvariantCulture,
208 + "File {0} exceeds maximum file size " +
209 + "for cabinet format.",
210 + file));
211 + }
212 +
213 + if (uncompressedBytesInFolder > 0)
214 + {
215 + // Automatically create a new folder if this file
216 + // won't fit in the current folder.
217 + bool nextFolder = uncompressedBytesInFolder
218 + + fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER;
219 +
220 + // Otherwise ask the client if it wants to
221 + // move to the next folder.
222 + if (!nextFolder)
223 + {
224 + object nextFolderOption = streamContext.GetOption(
225 + "nextFolder",
226 + new object[] { file, this.currentFolderNumber });
227 + nextFolder = Convert.ToBoolean(
228 + nextFolderOption, CultureInfo.InvariantCulture);
229 + }
230 +
231 + if (nextFolder)
232 + {
233 + this.FlushFolder();
234 + uncompressedBytesInFolder = 0;
235 + }
236 + }
237 +
238 + if (this.currentFolderTotalBytes > 0)
239 + {
240 + this.currentFolderTotalBytes = 0;
241 + this.currentFolderNumber++;
242 + uncompressedBytesInFolder = 0;
243 + }
244 +
245 + this.currentFileName = file;
246 + this.currentFileNumber++;
247 +
248 + this.currentFileTotalBytes = fileStream.Length;
249 + this.currentFileBytesProcessed = 0;
250 + this.OnProgress(ArchiveProgressType.StartFile);
251 +
252 + uncompressedBytesInFolder += fileStream.Length;
253 +
254 + this.AddFile(
255 + file,
256 + fileStream,
257 + attributes,
258 + lastWriteTime,
259 + false,
260 + this.CompressionLevel);
261 + }
262 +
263 + this.FlushFolder();
264 + this.FlushCabinet();
265 + }
266 + finally
267 + {
268 + if (this.CabStream != null)
269 + {
270 + this.context.CloseArchiveWriteStream(
271 + this.currentArchiveNumber,
272 + this.currentArchiveName,
273 + this.CabStream);
274 + this.CabStream = null;
275 + }
276 +
277 + if (this.FileStream != null)
278 + {
279 + this.context.CloseFileReadStream(
280 + this.currentFileName, this.FileStream);
281 + this.FileStream = null;
282 + }
283 + this.context = null;
284 +
285 + if (this.fciHandle != null)
286 + {
287 + this.fciHandle.Dispose();
288 + this.fciHandle = null;
289 + }
290 + }
291 + }
292 + }
293 +
294 + internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
295 + {
296 + if (this.CabNumbers.ContainsKey(path))
297 + {
298 + Stream stream = this.CabStream;
299 + if (stream == null)
300 + {
301 + short cabNumber = this.CabNumbers[path];
302 +
303 + this.currentFolderTotalBytes = 0;
304 +
305 + stream = this.context.OpenArchiveWriteStream(cabNumber, path, true, this.CabEngine);
306 + if (stream == null)
307 + {
308 + throw new FileNotFoundException(
309 + String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
310 + }
311 + this.currentArchiveName = path;
312 +
313 + this.currentArchiveTotalBytes = Math.Min(
314 + this.totalFolderBytesProcessedInCurrentCab, this.maxCabBytes);
315 + this.currentArchiveBytesProcessed = 0;
316 +
317 + this.OnProgress(ArchiveProgressType.StartArchive);
318 + this.CabStream = stream;
319 + }
320 + path = CabWorker.CabStreamName;
321 + }
322 + else if (path == CabPacker.TempStreamName)
323 + {
324 + // Opening memory stream for a temp file.
325 + Stream stream = new MemoryStream();
326 + this.tempStreams.Add(stream);
327 + int streamHandle = this.StreamHandles.AllocHandle(stream);
328 + err = 0;
329 + return streamHandle;
330 + }
331 + else if (path != CabWorker.CabStreamName)
332 + {
333 + // Opening a file on disk for a temp file.
334 + path = Path.Combine(Path.GetTempPath(), path);
335 + Stream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
336 + this.tempStreams.Add(stream);
337 + stream = new DuplicateStream(stream);
338 + int streamHandle = this.StreamHandles.AllocHandle(stream);
339 + err = 0;
340 + return streamHandle;
341 + }
342 + return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
343 + }
344 +
345 + internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
346 + {
347 + int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
348 + if (count > 0 && err == 0)
349 + {
350 + Stream stream = this.StreamHandles[streamHandle];
351 + if (DuplicateStream.OriginalStream(stream) ==
352 + DuplicateStream.OriginalStream(this.CabStream))
353 + {
354 + this.currentArchiveBytesProcessed += cb;
355 + if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
356 + {
357 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
358 + }
359 + }
360 + }
361 + return count;
362 + }
363 +
364 + internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
365 + {
366 + Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
367 +
368 + if (stream == DuplicateStream.OriginalStream(this.FileStream))
369 + {
370 + this.context.CloseFileReadStream(this.currentFileName, stream);
371 + this.FileStream = null;
372 + long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
373 + this.currentFileBytesProcessed += remainder;
374 + this.fileBytesProcessed += remainder;
375 + this.OnProgress(ArchiveProgressType.FinishFile);
376 +
377 + this.currentFileTotalBytes = 0;
378 + this.currentFileBytesProcessed = 0;
379 + this.currentFileName = null;
380 + }
381 + else if (stream == DuplicateStream.OriginalStream(this.CabStream))
382 + {
383 + if (stream.CanWrite)
384 + {
385 + stream.Flush();
386 + }
387 +
388 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
389 + this.OnProgress(ArchiveProgressType.FinishArchive);
390 + this.currentArchiveNumber++;
391 + this.totalArchives++;
392 +
393 + this.context.CloseArchiveWriteStream(
394 + this.currentArchiveNumber,
395 + this.currentArchiveName,
396 + stream);
397 +
398 + this.currentArchiveName = this.NextCabinetName;
399 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
400 + this.totalFolderBytesProcessedInCurrentCab = 0;
401 +
402 + this.CabStream = null;
403 + }
404 + else // Must be a temp stream
405 + {
406 + stream.Close();
407 + this.tempStreams.Remove(stream);
408 + }
409 + return base.CabCloseStreamEx(streamHandle, out err, pv);
410 + }
411 +
412 + /// <summary>
413 + /// Disposes of resources allocated by the cabinet engine.
414 + /// </summary>
415 + /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
416 + /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
417 + /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
418 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
419 + protected override void Dispose(bool disposing)
420 + {
421 + try
422 + {
423 + if (disposing)
424 + {
425 + if (this.fciHandle != null)
426 + {
427 + this.fciHandle.Dispose();
428 + this.fciHandle = null;
429 + }
430 + }
431 + }
432 + finally
433 + {
434 + base.Dispose(disposing);
435 + }
436 + }
437 +
438 + private static NativeMethods.FCI.TCOMP GetCompressionType(CompressionLevel compLevel)
439 + {
440 + if (compLevel < CompressionLevel.Min)
441 + {
442 + return NativeMethods.FCI.TCOMP.TYPE_NONE;
443 + }
444 + else
445 + {
446 + if (compLevel > CompressionLevel.Max)
447 + {
448 + compLevel = CompressionLevel.Max;
449 + }
450 +
451 + int lzxWindowMax =
452 + ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_HI >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW) -
453 + ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW);
454 + int lzxWindow = lzxWindowMax *
455 + (compLevel - CompressionLevel.Min) / (CompressionLevel.Max - CompressionLevel.Min);
456 +
457 + return (NativeMethods.FCI.TCOMP) ((int) NativeMethods.FCI.TCOMP.TYPE_LZX |
458 + ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO +
459 + (lzxWindow << (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW)));
460 + }
461 + }
462 +
463 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
464 + private void AddFile(
465 + string name,
466 + Stream stream,
467 + FileAttributes attributes,
468 + DateTime lastWriteTime,
469 + bool execute,
470 + CompressionLevel compLevel)
471 + {
472 + this.FileStream = stream;
473 + this.fileAttributes = attributes &
474 + (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
475 + this.fileLastWriteTime = lastWriteTime;
476 + this.currentFileName = name;
477 +
478 + NativeMethods.FCI.TCOMP tcomp = CabPacker.GetCompressionType(compLevel);
479 +
480 + IntPtr namePtr = IntPtr.Zero;
481 + try
482 + {
483 + Encoding nameEncoding = Encoding.ASCII;
484 + if (Encoding.UTF8.GetByteCount(name) > name.Length)
485 + {
486 + nameEncoding = Encoding.UTF8;
487 + this.fileAttributes |= FileAttributes.Normal; // _A_NAME_IS_UTF
488 + }
489 +
490 + byte[] nameBytes = nameEncoding.GetBytes(name);
491 + namePtr = Marshal.AllocHGlobal(nameBytes.Length + 1);
492 + Marshal.Copy(nameBytes, 0, namePtr, nameBytes.Length);
493 + Marshal.WriteByte(namePtr, nameBytes.Length, 0);
494 +
495 + this.Erf.Clear();
496 + NativeMethods.FCI.AddFile(
497 + this.fciHandle,
498 + String.Empty,
499 + namePtr,
500 + execute,
501 + this.fciGetNextCabinet,
502 + this.fciCreateStatus,
503 + this.fciGetOpenInfo,
504 + tcomp);
505 + }
506 + finally
507 + {
508 + if (namePtr != IntPtr.Zero)
509 + {
510 + Marshal.FreeHGlobal(namePtr);
511 + }
512 + }
513 +
514 + this.CheckError(false);
515 + this.FileStream = null;
516 + this.currentFileName = null;
517 + }
518 +
519 + private void FlushFolder()
520 + {
521 + this.Erf.Clear();
522 + NativeMethods.FCI.FlushFolder(this.fciHandle, this.fciGetNextCabinet, this.fciCreateStatus);
523 + this.CheckError(false);
524 + }
525 +
526 + private void FlushCabinet()
527 + {
528 + this.Erf.Clear();
529 + NativeMethods.FCI.FlushCabinet(this.fciHandle, false, this.fciGetNextCabinet, this.fciCreateStatus);
530 + this.CheckError(false);
531 + }
532 +
533 + private int CabGetOpenInfo(
534 + string path,
535 + out short date,
536 + out short time,
537 + out short attribs,
538 + out int err,
539 + IntPtr pv)
540 + {
541 + CompressionEngine.DateTimeToDosDateAndTime(this.fileLastWriteTime, out date, out time);
542 + attribs = (short) this.fileAttributes;
543 +
544 + Stream stream = this.FileStream;
545 + this.FileStream = new DuplicateStream(stream);
546 + int streamHandle = this.StreamHandles.AllocHandle(stream);
547 + err = 0;
548 + return streamHandle;
549 + }
550 +
551 + private int CabFilePlaced(
552 + IntPtr pccab,
553 + string filePath,
554 + long fileSize,
555 + int continuation,
556 + IntPtr pv)
557 + {
558 + return 0;
559 + }
560 +
561 + private int CabGetNextCabinet(IntPtr pccab, uint prevCabSize, IntPtr pv)
562 + {
563 + NativeMethods.FCI.CCAB nextCcab = new NativeMethods.FCI.CCAB();
564 + Marshal.PtrToStructure(pccab, nextCcab);
565 +
566 + nextCcab.szDisk = String.Empty;
567 + nextCcab.szCab = this.context.GetArchiveName(nextCcab.iCab);
568 + this.CabNumbers[nextCcab.szCab] = (short) nextCcab.iCab;
569 + this.NextCabinetName = nextCcab.szCab;
570 +
571 + Marshal.StructureToPtr(nextCcab, pccab, false);
572 + return 1;
573 + }
574 +
575 + private int CabCreateStatus(NativeMethods.FCI.STATUS typeStatus, uint cb1, uint cb2, IntPtr pv)
576 + {
577 + switch (typeStatus)
578 + {
579 + case NativeMethods.FCI.STATUS.FILE:
580 + if (cb2 > 0 && this.currentFileBytesProcessed < this.currentFileTotalBytes)
581 + {
582 + if (this.currentFileBytesProcessed + cb2 > this.currentFileTotalBytes)
583 + {
584 + cb2 = (uint) this.currentFileTotalBytes - (uint) this.currentFileBytesProcessed;
585 + }
586 + this.currentFileBytesProcessed += cb2;
587 + this.fileBytesProcessed += cb2;
588 +
589 + this.OnProgress(ArchiveProgressType.PartialFile);
590 + }
591 + break;
592 +
593 + case NativeMethods.FCI.STATUS.FOLDER:
594 + if (cb1 == 0)
595 + {
596 + this.currentFolderTotalBytes = cb2 - this.totalFolderBytesProcessedInCurrentCab;
597 + this.totalFolderBytesProcessedInCurrentCab = cb2;
598 + }
599 + else if (this.currentFolderTotalBytes == 0)
600 + {
601 + this.OnProgress(ArchiveProgressType.PartialArchive);
602 + }
603 + break;
604 +
605 + case NativeMethods.FCI.STATUS.CABINET:
606 + break;
607 + }
608 + return 0;
609 + }
610 +
611 + private int CabGetTempFile(IntPtr tempNamePtr, int tempNameSize, IntPtr pv)
612 + {
613 + string tempFileName;
614 + if (this.UseTempFiles)
615 + {
616 + tempFileName = Path.GetFileName(Path.GetTempFileName());
617 + }
618 + else
619 + {
620 + tempFileName = CabPacker.TempStreamName;
621 + }
622 +
623 + byte[] tempNameBytes = Encoding.ASCII.GetBytes(tempFileName);
624 + if (tempNameBytes.Length >= tempNameSize)
625 + {
626 + return -1;
627 + }
628 +
629 + Marshal.Copy(tempNameBytes, 0, tempNamePtr, tempNameBytes.Length);
630 + Marshal.WriteByte(tempNamePtr, tempNameBytes.Length, 0); // null-terminator
631 + return 1;
632 + }
633 +
634 + private int CabDeleteFile(string path, out int err, IntPtr pv)
635 + {
636 + try
637 + {
638 + // Deleting a temp file - don't bother if it is only a memory stream.
639 + if (path != CabPacker.TempStreamName)
640 + {
641 + path = Path.Combine(Path.GetTempPath(), path);
642 + File.Delete(path);
643 + }
644 + }
645 + catch (IOException)
646 + {
647 + // Failure to delete a temp file is not fatal.
648 + }
649 + err = 0;
650 + return 1;
651 + }
652 + }
653 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabUnpacker.cs new
+566
@@ -0,0 +1,566 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using System.Globalization;
10 + using System.Runtime.InteropServices;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + internal class CabUnpacker : CabWorker
14 + {
15 + private NativeMethods.FDI.Handle fdiHandle;
16 +
17 + // These delegates need to be saved as member variables
18 + // so that they don't get GC'd.
19 + private NativeMethods.FDI.PFNALLOC fdiAllocMemHandler;
20 + private NativeMethods.FDI.PFNFREE fdiFreeMemHandler;
21 + private NativeMethods.FDI.PFNOPEN fdiOpenStreamHandler;
22 + private NativeMethods.FDI.PFNREAD fdiReadStreamHandler;
23 + private NativeMethods.FDI.PFNWRITE fdiWriteStreamHandler;
24 + private NativeMethods.FDI.PFNCLOSE fdiCloseStreamHandler;
25 + private NativeMethods.FDI.PFNSEEK fdiSeekStreamHandler;
26 +
27 + private IUnpackStreamContext context;
28 +
29 + private List<ArchiveFileInfo> fileList;
30 +
31 + private int folderId;
32 +
33 + private Predicate<string> filter;
34 +
35 + public CabUnpacker(CabEngine cabEngine)
36 + : base(cabEngine)
37 + {
38 + this.fdiAllocMemHandler = this.CabAllocMem;
39 + this.fdiFreeMemHandler = this.CabFreeMem;
40 + this.fdiOpenStreamHandler = this.CabOpenStream;
41 + this.fdiReadStreamHandler = this.CabReadStream;
42 + this.fdiWriteStreamHandler = this.CabWriteStream;
43 + this.fdiCloseStreamHandler = this.CabCloseStream;
44 + this.fdiSeekStreamHandler = this.CabSeekStream;
45 +
46 + this.fdiHandle = NativeMethods.FDI.Create(
47 + this.fdiAllocMemHandler,
48 + this.fdiFreeMemHandler,
49 + this.fdiOpenStreamHandler,
50 + this.fdiReadStreamHandler,
51 + this.fdiWriteStreamHandler,
52 + this.fdiCloseStreamHandler,
53 + this.fdiSeekStreamHandler,
54 + NativeMethods.FDI.CPU_80386,
55 + this.ErfHandle.AddrOfPinnedObject());
56 + if (this.Erf.Error)
57 + {
58 + int error = this.Erf.Oper;
59 + int errorCode = this.Erf.Type;
60 + this.ErfHandle.Free();
61 + throw new CabException(
62 + error,
63 + errorCode,
64 + CabException.GetErrorMessage(error, errorCode, true));
65 + }
66 + }
67 +
68 + public bool IsArchive(Stream stream)
69 + {
70 + if (stream == null)
71 + {
72 + throw new ArgumentNullException("stream");
73 + }
74 +
75 + lock (this)
76 + {
77 + short id;
78 + int folderCount, fileCount;
79 + return this.IsCabinet(stream, out id, out folderCount, out fileCount);
80 + }
81 + }
82 +
83 + public IList<ArchiveFileInfo> GetFileInfo(
84 + IUnpackStreamContext streamContext,
85 + Predicate<string> fileFilter)
86 + {
87 + if (streamContext == null)
88 + {
89 + throw new ArgumentNullException("streamContext");
90 + }
91 +
92 + lock (this)
93 + {
94 + this.context = streamContext;
95 + this.filter = fileFilter;
96 + this.NextCabinetName = String.Empty;
97 + this.fileList = new List<ArchiveFileInfo>();
98 + bool tmpSuppress = this.SuppressProgressEvents;
99 + this.SuppressProgressEvents = true;
100 + try
101 + {
102 + for (short cabNumber = 0;
103 + this.NextCabinetName != null;
104 + cabNumber++)
105 + {
106 + this.Erf.Clear();
107 + this.CabNumbers[this.NextCabinetName] = cabNumber;
108 +
109 + NativeMethods.FDI.Copy(
110 + this.fdiHandle,
111 + this.NextCabinetName,
112 + String.Empty,
113 + 0,
114 + this.CabListNotify,
115 + IntPtr.Zero,
116 + IntPtr.Zero);
117 + this.CheckError(true);
118 + }
119 +
120 + List<ArchiveFileInfo> tmpFileList = this.fileList;
121 + this.fileList = null;
122 + return tmpFileList.AsReadOnly();
123 + }
124 + finally
125 + {
126 + this.SuppressProgressEvents = tmpSuppress;
127 +
128 + if (this.CabStream != null)
129 + {
130 + this.context.CloseArchiveReadStream(
131 + this.currentArchiveNumber,
132 + this.currentArchiveName,
133 + this.CabStream);
134 + this.CabStream = null;
135 + }
136 +
137 + this.context = null;
138 + }
139 + }
140 + }
141 +
142 + public void Unpack(
143 + IUnpackStreamContext streamContext,
144 + Predicate<string> fileFilter)
145 + {
146 + lock (this)
147 + {
148 + IList<ArchiveFileInfo> files =
149 + this.GetFileInfo(streamContext, fileFilter);
150 +
151 + this.ResetProgressData();
152 +
153 + if (files != null)
154 + {
155 + this.totalFiles = files.Count;
156 +
157 + for (int i = 0; i < files.Count; i++)
158 + {
159 + this.totalFileBytes += files[i].Length;
160 + if (files[i].ArchiveNumber >= this.totalArchives)
161 + {
162 + int totalArchives = files[i].ArchiveNumber + 1;
163 + this.totalArchives = (short) totalArchives;
164 + }
165 + }
166 + }
167 +
168 + this.context = streamContext;
169 + this.fileList = null;
170 + this.NextCabinetName = String.Empty;
171 + this.folderId = -1;
172 + this.currentFileNumber = -1;
173 +
174 + try
175 + {
176 + for (short cabNumber = 0;
177 + this.NextCabinetName != null;
178 + cabNumber++)
179 + {
180 + this.Erf.Clear();
181 + this.CabNumbers[this.NextCabinetName] = cabNumber;
182 +
183 + NativeMethods.FDI.Copy(
184 + this.fdiHandle,
185 + this.NextCabinetName,
186 + String.Empty,
187 + 0,
188 + this.CabExtractNotify,
189 + IntPtr.Zero,
190 + IntPtr.Zero);
191 + this.CheckError(true);
192 + }
193 + }
194 + finally
195 + {
196 + if (this.CabStream != null)
197 + {
198 + this.context.CloseArchiveReadStream(
199 + this.currentArchiveNumber,
200 + this.currentArchiveName,
201 + this.CabStream);
202 + this.CabStream = null;
203 + }
204 +
205 + if (this.FileStream != null)
206 + {
207 + this.context.CloseFileWriteStream(this.currentFileName, this.FileStream, FileAttributes.Normal, DateTime.Now);
208 + this.FileStream = null;
209 + }
210 +
211 + this.context = null;
212 + }
213 + }
214 + }
215 +
216 + internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
217 + {
218 + if (this.CabNumbers.ContainsKey(path))
219 + {
220 + Stream stream = this.CabStream;
221 + if (stream == null)
222 + {
223 + short cabNumber = this.CabNumbers[path];
224 +
225 + stream = this.context.OpenArchiveReadStream(cabNumber, path, this.CabEngine);
226 + if (stream == null)
227 + {
228 + throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
229 + }
230 + this.currentArchiveName = path;
231 + this.currentArchiveNumber = cabNumber;
232 + if (this.totalArchives <= this.currentArchiveNumber)
233 + {
234 + int totalArchives = this.currentArchiveNumber + 1;
235 + this.totalArchives = (short) totalArchives;
236 + }
237 + this.currentArchiveTotalBytes = stream.Length;
238 + this.currentArchiveBytesProcessed = 0;
239 +
240 + if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
241 + {
242 + this.OnProgress(ArchiveProgressType.StartArchive);
243 + }
244 + this.CabStream = stream;
245 + }
246 + path = CabWorker.CabStreamName;
247 + }
248 + return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
249 + }
250 +
251 + internal override int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
252 + {
253 + int count = base.CabReadStreamEx(streamHandle, memory, cb, out err, pv);
254 + if (err == 0 && this.CabStream != null)
255 + {
256 + if (this.fileList == null)
257 + {
258 + Stream stream = this.StreamHandles[streamHandle];
259 + if (DuplicateStream.OriginalStream(stream) ==
260 + DuplicateStream.OriginalStream(this.CabStream))
261 + {
262 + this.currentArchiveBytesProcessed += cb;
263 + if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
264 + {
265 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
266 + }
267 + }
268 + }
269 + }
270 + return count;
271 + }
272 +
273 + internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
274 + {
275 + int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
276 + if (count > 0 && err == 0)
277 + {
278 + this.currentFileBytesProcessed += cb;
279 + this.fileBytesProcessed += cb;
280 + this.OnProgress(ArchiveProgressType.PartialFile);
281 + }
282 + return count;
283 + }
284 +
285 + internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
286 + {
287 + Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
288 +
289 + if (stream == DuplicateStream.OriginalStream(this.CabStream))
290 + {
291 + if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
292 + {
293 + this.OnProgress(ArchiveProgressType.FinishArchive);
294 + }
295 +
296 + this.context.CloseArchiveReadStream(this.currentArchiveNumber, this.currentArchiveName, stream);
297 +
298 + this.currentArchiveName = this.NextCabinetName;
299 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
300 +
301 + this.CabStream = null;
302 + }
303 + return base.CabCloseStreamEx(streamHandle, out err, pv);
304 + }
305 +
306 + /// <summary>
307 + /// Disposes of resources allocated by the cabinet engine.
308 + /// </summary>
309 + /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
310 + /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
311 + /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
312 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
313 + protected override void Dispose(bool disposing)
314 + {
315 + try
316 + {
317 + if (disposing)
318 + {
319 + if (this.fdiHandle != null)
320 + {
321 + this.fdiHandle.Dispose();
322 + this.fdiHandle = null;
323 + }
324 + }
325 + }
326 + finally
327 + {
328 + base.Dispose(disposing);
329 + }
330 + }
331 +
332 + private static string GetFileName(NativeMethods.FDI.NOTIFICATION notification)
333 + {
334 + bool utf8Name = (notification.attribs & (ushort) FileAttributes.Normal) != 0; // _A_NAME_IS_UTF
335 +
336 + // Non-utf8 names should be completely ASCII. But for compatibility with
337 + // legacy tools, interpret them using the current (Default) ANSI codepage.
338 + Encoding nameEncoding = utf8Name ? Encoding.UTF8 : Encoding.Default;
339 +
340 + // Find how many bytes are in the string.
341 + // Unfortunately there is no faster way.
342 + int nameBytesCount = 0;
343 + while (Marshal.ReadByte(notification.psz1, nameBytesCount) != 0)
344 + {
345 + nameBytesCount++;
346 + }
347 +
348 + byte[] nameBytes = new byte[nameBytesCount];
349 + Marshal.Copy(notification.psz1, nameBytes, 0, nameBytesCount);
350 + string name = nameEncoding.GetString(nameBytes);
351 + if (Path.IsPathRooted(name))
352 + {
353 + name = name.Replace("" + Path.VolumeSeparatorChar, "");
354 + }
355 +
356 + return name;
357 + }
358 +
359 + private bool IsCabinet(Stream cabStream, out short id, out int cabFolderCount, out int fileCount)
360 + {
361 + int streamHandle = this.StreamHandles.AllocHandle(cabStream);
362 + try
363 + {
364 + this.Erf.Clear();
365 + NativeMethods.FDI.CABINFO fdici;
366 + bool isCabinet = 0 != NativeMethods.FDI.IsCabinet(this.fdiHandle, streamHandle, out fdici);
367 +
368 + if (this.Erf.Error)
369 + {
370 + if (((NativeMethods.FDI.ERROR) this.Erf.Oper) == NativeMethods.FDI.ERROR.UNKNOWN_CABINET_VERSION)
371 + {
372 + isCabinet = false;
373 + }
374 + else
375 + {
376 + throw new CabException(
377 + this.Erf.Oper,
378 + this.Erf.Type,
379 + CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, true));
380 + }
381 + }
382 +
383 + id = fdici.setID;
384 + cabFolderCount = (int) fdici.cFolders;
385 + fileCount = (int) fdici.cFiles;
386 + return isCabinet;
387 + }
388 + finally
389 + {
390 + this.StreamHandles.FreeHandle(streamHandle);
391 + }
392 + }
393 +
394 + private int CabListNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
395 + {
396 + switch (notificationType)
397 + {
398 + case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
399 + {
400 + string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
401 + this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
402 + return 0; // Continue
403 + }
404 + case NativeMethods.FDI.NOTIFICATIONTYPE.PARTIAL_FILE:
405 + {
406 + // This notification can occur when examining the contents of a non-first cab file.
407 + return 0; // Continue
408 + }
409 + case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
410 + {
411 + //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
412 +
413 + string name = CabUnpacker.GetFileName(notification);
414 +
415 + if (this.filter == null || this.filter(name))
416 + {
417 + if (this.fileList != null)
418 + {
419 + FileAttributes attributes = (FileAttributes) notification.attribs &
420 + (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
421 + if (attributes == (FileAttributes) 0)
422 + {
423 + attributes = FileAttributes.Normal;
424 + }
425 + DateTime lastWriteTime;
426 + CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
427 + long length = notification.cb;
428 +
429 + CabFileInfo fileInfo = new CabFileInfo(
430 + name,
431 + notification.iFolder,
432 + notification.iCabinet,
433 + attributes,
434 + lastWriteTime,
435 + length);
436 + this.fileList.Add(fileInfo);
437 + this.currentFileNumber = this.fileList.Count - 1;
438 + this.fileBytesProcessed += notification.cb;
439 + }
440 + }
441 +
442 + this.totalFiles++;
443 + this.totalFileBytes += notification.cb;
444 + return 0; // Continue
445 + }
446 + }
447 + return 0;
448 + }
449 +
450 + private int CabExtractNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
451 + {
452 + switch (notificationType)
453 + {
454 + case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
455 + {
456 + if (this.NextCabinetName != null && this.NextCabinetName.StartsWith("?", StringComparison.Ordinal))
457 + {
458 + // We are just continuing the copy of a file that spanned cabinets.
459 + // The next cabinet name needs to be preserved.
460 + this.NextCabinetName = this.NextCabinetName.Substring(1);
461 + }
462 + else
463 + {
464 + string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
465 + this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
466 + }
467 + return 0; // Continue
468 + }
469 + case NativeMethods.FDI.NOTIFICATIONTYPE.NEXT_CABINET:
470 + {
471 + string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
472 + this.CabNumbers[nextCab] = (short) notification.iCabinet;
473 + this.NextCabinetName = "?" + this.NextCabinetName;
474 + return 0; // Continue
475 + }
476 + case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
477 + {
478 + return this.CabExtractCopyFile(notification);
479 + }
480 + case NativeMethods.FDI.NOTIFICATIONTYPE.CLOSE_FILE_INFO:
481 + {
482 + return this.CabExtractCloseFile(notification);
483 + }
484 + }
485 + return 0;
486 + }
487 +
488 + private int CabExtractCopyFile(NativeMethods.FDI.NOTIFICATION notification)
489 + {
490 + if (notification.iFolder != this.folderId)
491 + {
492 + if (notification.iFolder != -3) // -3 is a special folderId used when continuing a folder from a previous cab
493 + {
494 + if (this.folderId != -1) // -1 means we just started the extraction sequence
495 + {
496 + this.currentFolderNumber++;
497 + }
498 + }
499 + this.folderId = notification.iFolder;
500 + }
501 +
502 + //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
503 +
504 + string name = CabUnpacker.GetFileName(notification);
505 +
506 + if (this.filter == null || this.filter(name))
507 + {
508 + this.currentFileNumber++;
509 + this.currentFileName = name;
510 +
511 + this.currentFileBytesProcessed = 0;
512 + this.currentFileTotalBytes = notification.cb;
513 + this.OnProgress(ArchiveProgressType.StartFile);
514 +
515 + DateTime lastWriteTime;
516 + CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
517 +
518 + Stream stream = this.context.OpenFileWriteStream(name, notification.cb, lastWriteTime);
519 + if (stream != null)
520 + {
521 + this.FileStream = stream;
522 + int streamHandle = this.StreamHandles.AllocHandle(stream);
523 + return streamHandle;
524 + }
525 + else
526 + {
527 + this.fileBytesProcessed += notification.cb;
528 + this.OnProgress(ArchiveProgressType.FinishFile);
529 + this.currentFileName = null;
530 + }
531 + }
532 + return 0; // Continue
533 + }
534 +
535 + private int CabExtractCloseFile(NativeMethods.FDI.NOTIFICATION notification)
536 + {
537 + Stream stream = this.StreamHandles[notification.hf];
538 + this.StreamHandles.FreeHandle(notification.hf);
539 +
540 + //bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
541 +
542 + string name = CabUnpacker.GetFileName(notification);
543 +
544 + FileAttributes attributes = (FileAttributes) notification.attribs &
545 + (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
546 + if (attributes == (FileAttributes) 0)
547 + {
548 + attributes = FileAttributes.Normal;
549 + }
550 + DateTime lastWriteTime;
551 + CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
552 +
553 + stream.Flush();
554 + this.context.CloseFileWriteStream(name, stream, attributes, lastWriteTime);
555 + this.FileStream = null;
556 +
557 + long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
558 + this.currentFileBytesProcessed += remainder;
559 + this.fileBytesProcessed += remainder;
560 + this.OnProgress(ArchiveProgressType.FinishFile);
561 + this.currentFileName = null;
562 +
563 + return 1; // Continue
564 + }
565 + }
566 +}
src/dtf/WixToolset.Dtf.Compression.Cab/CabWorker.cs new
+337
@@ -0,0 +1,337 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.IO;
7 + using System.IO.IsolatedStorage;
8 + using System.Text;
9 + using System.Security;
10 + using System.Collections.Generic;
11 + using System.Runtime.InteropServices;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + internal abstract class CabWorker : IDisposable
15 + {
16 + internal const string CabStreamName = "%%CAB%%";
17 +
18 + private CabEngine cabEngine;
19 +
20 + private HandleManager<Stream> streamHandles;
21 + private Stream cabStream;
22 + private Stream fileStream;
23 +
24 + private NativeMethods.ERF erf;
25 + private GCHandle erfHandle;
26 +
27 + private IDictionary<string, short> cabNumbers;
28 + private string nextCabinetName;
29 +
30 + private bool suppressProgressEvents;
31 +
32 + private byte[] buf;
33 +
34 + // Progress data
35 + protected string currentFileName;
36 + protected int currentFileNumber;
37 + protected int totalFiles;
38 + protected long currentFileBytesProcessed;
39 + protected long currentFileTotalBytes;
40 + protected short currentFolderNumber;
41 + protected long currentFolderTotalBytes;
42 + protected string currentArchiveName;
43 + protected short currentArchiveNumber;
44 + protected short totalArchives;
45 + protected long currentArchiveBytesProcessed;
46 + protected long currentArchiveTotalBytes;
47 + protected long fileBytesProcessed;
48 + protected long totalFileBytes;
49 +
50 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
51 + protected CabWorker(CabEngine cabEngine)
52 + {
53 + this.cabEngine = cabEngine;
54 + this.streamHandles = new HandleManager<Stream>();
55 + this.erf = new NativeMethods.ERF();
56 + this.erfHandle = GCHandle.Alloc(this.erf, GCHandleType.Pinned);
57 + this.cabNumbers = new Dictionary<string, short>(1);
58 +
59 + // 32K seems to be the size of the largest chunks processed by cabinet.dll.
60 + // But just in case, this buffer will auto-enlarge.
61 + this.buf = new byte[32768];
62 + }
63 +
64 + ~CabWorker()
65 + {
66 + this.Dispose(false);
67 + }
68 +
69 + public CabEngine CabEngine
70 + {
71 + get
72 + {
73 + return this.cabEngine;
74 + }
75 + }
76 +
77 + internal NativeMethods.ERF Erf
78 + {
79 + get
80 + {
81 + return this.erf;
82 + }
83 + }
84 +
85 + internal GCHandle ErfHandle
86 + {
87 + get
88 + {
89 + return this.erfHandle;
90 + }
91 + }
92 +
93 + internal HandleManager<Stream> StreamHandles
94 + {
95 + get
96 + {
97 + return this.streamHandles;
98 + }
99 + }
100 +
101 + internal bool SuppressProgressEvents
102 + {
103 + get
104 + {
105 + return this.suppressProgressEvents;
106 + }
107 +
108 + set
109 + {
110 + this.suppressProgressEvents = value;
111 + }
112 + }
113 +
114 + internal IDictionary<string, short> CabNumbers
115 + {
116 + get
117 + {
118 + return this.cabNumbers;
119 + }
120 + }
121 +
122 + internal string NextCabinetName
123 + {
124 + get
125 + {
126 + return this.nextCabinetName;
127 + }
128 +
129 + set
130 + {
131 + this.nextCabinetName = value;
132 + }
133 + }
134 +
135 + internal Stream CabStream
136 + {
137 + get
138 + {
139 + return this.cabStream;
140 + }
141 +
142 + set
143 + {
144 + this.cabStream = value;
145 + }
146 + }
147 +
148 + internal Stream FileStream
149 + {
150 + get
151 + {
152 + return this.fileStream;
153 + }
154 +
155 + set
156 + {
157 + this.fileStream = value;
158 + }
159 + }
160 +
161 + public void Dispose()
162 + {
163 + this.Dispose(true);
164 + GC.SuppressFinalize(this);
165 + }
166 +
167 + protected void ResetProgressData()
168 + {
169 + this.currentFileName = null;
170 + this.currentFileNumber = 0;
171 + this.totalFiles = 0;
172 + this.currentFileBytesProcessed = 0;
173 + this.currentFileTotalBytes = 0;
174 + this.currentFolderNumber = 0;
175 + this.currentFolderTotalBytes = 0;
176 + this.currentArchiveName = null;
177 + this.currentArchiveNumber = 0;
178 + this.totalArchives = 0;
179 + this.currentArchiveBytesProcessed = 0;
180 + this.currentArchiveTotalBytes = 0;
181 + this.fileBytesProcessed = 0;
182 + this.totalFileBytes = 0;
183 + }
184 +
185 + protected void OnProgress(ArchiveProgressType progressType)
186 + {
187 + if (!this.suppressProgressEvents)
188 + {
189 + ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
190 + progressType,
191 + this.currentFileName,
192 + this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
193 + this.totalFiles,
194 + this.currentFileBytesProcessed,
195 + this.currentFileTotalBytes,
196 + this.currentArchiveName,
197 + this.currentArchiveNumber,
198 + this.totalArchives,
199 + this.currentArchiveBytesProcessed,
200 + this.currentArchiveTotalBytes,
201 + this.fileBytesProcessed,
202 + this.totalFileBytes);
203 + this.CabEngine.ReportProgress(e);
204 + }
205 + }
206 +
207 + internal IntPtr CabAllocMem(int byteCount)
208 + {
209 + IntPtr memPointer = Marshal.AllocHGlobal((IntPtr) byteCount);
210 + return memPointer;
211 + }
212 +
213 + internal void CabFreeMem(IntPtr memPointer)
214 + {
215 + Marshal.FreeHGlobal(memPointer);
216 + }
217 +
218 + internal int CabOpenStream(string path, int openFlags, int shareMode)
219 + {
220 + int err; return this.CabOpenStreamEx(path, openFlags, shareMode, out err, IntPtr.Zero);
221 + }
222 +
223 + internal virtual int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
224 + {
225 + path = path.Trim();
226 + Stream stream = this.cabStream;
227 + this.cabStream = new DuplicateStream(stream);
228 + int streamHandle = this.streamHandles.AllocHandle(stream);
229 + err = 0;
230 + return streamHandle;
231 + }
232 +
233 + internal int CabReadStream(int streamHandle, IntPtr memory, int cb)
234 + {
235 + int err; return this.CabReadStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
236 + }
237 +
238 + internal virtual int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
239 + {
240 + Stream stream = this.streamHandles[streamHandle];
241 + int count = (int) cb;
242 + if (count > this.buf.Length)
243 + {
244 + this.buf = new byte[count];
245 + }
246 + count = stream.Read(this.buf, 0, count);
247 + Marshal.Copy(this.buf, 0, memory, count);
248 + err = 0;
249 + return count;
250 + }
251 +
252 + internal int CabWriteStream(int streamHandle, IntPtr memory, int cb)
253 + {
254 + int err; return this.CabWriteStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
255 + }
256 +
257 + internal virtual int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
258 + {
259 + Stream stream = this.streamHandles[streamHandle];
260 + int count = (int) cb;
261 + if (count > this.buf.Length)
262 + {
263 + this.buf = new byte[count];
264 + }
265 + Marshal.Copy(memory, this.buf, 0, count);
266 + stream.Write(this.buf, 0, count);
267 + err = 0;
268 + return cb;
269 + }
270 +
271 + internal int CabCloseStream(int streamHandle)
272 + {
273 + int err; return this.CabCloseStreamEx(streamHandle, out err, IntPtr.Zero);
274 + }
275 +
276 + internal virtual int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
277 + {
278 + this.streamHandles.FreeHandle(streamHandle);
279 + err = 0;
280 + return 0;
281 + }
282 +
283 + internal int CabSeekStream(int streamHandle, int offset, int seekOrigin)
284 + {
285 + int err; return this.CabSeekStreamEx(streamHandle, offset, seekOrigin, out err, IntPtr.Zero);
286 + }
287 +
288 + internal virtual int CabSeekStreamEx(int streamHandle, int offset, int seekOrigin, out int err, IntPtr pv)
289 + {
290 + Stream stream = this.streamHandles[streamHandle];
291 + offset = (int) stream.Seek(offset, (SeekOrigin) seekOrigin);
292 + err = 0;
293 + return offset;
294 + }
295 +
296 + /// <summary>
297 + /// Disposes of resources allocated by the cabinet engine.
298 + /// </summary>
299 + /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
300 + /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
301 + /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
302 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
303 + protected virtual void Dispose(bool disposing)
304 + {
305 + if (disposing)
306 + {
307 + if (this.cabStream != null)
308 + {
309 + this.cabStream.Close();
310 + this.cabStream = null;
311 + }
312 +
313 + if (this.fileStream != null)
314 + {
315 + this.fileStream.Close();
316 + this.fileStream = null;
317 + }
318 + }
319 +
320 + if (this.erfHandle.IsAllocated)
321 + {
322 + this.erfHandle.Free();
323 + }
324 + }
325 +
326 + protected void CheckError(bool extracting)
327 + {
328 + if (this.Erf.Error)
329 + {
330 + throw new CabException(
331 + this.Erf.Oper,
332 + this.Erf.Type,
333 + CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, extracting));
334 + }
335 + }
336 + }
337 +}
src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources
Binary files /dev/null and b/src/dtf/WixToolset.Dtf.Compression.Cab/Errors.resources differ
src/dtf/WixToolset.Dtf.Compression.Cab/Errors.txt new
+35
@@ -0,0 +1,35 @@
1 +;
2 +; Cabinet Error Messages
3 +;
4 +
5 +; Generic error message.
6 +1=Error code: {1}
7 +
8 +;
9 +; Cabinet creation messages - offset by 1000
10 +;
11 +1000=Unknown error creating cabinet.
12 +1001=Failure opening file to be stored in cabinet.
13 +1002=Failure reading file to be stored in cabinet.
14 +1003=Could not allocate enough memory to create cabinet.
15 +1004=Could not create a temporary file.
16 +1005=Unknown compression type.
17 +1006=Could not create cabinet file.
18 +1007=Client requested abort.
19 +1008=Failure compressing data.
20 +
21 +;
22 +; Cabinet extraction messages - offset by 2000
23 +;
24 +2000=Unknown error extracting cabinet.
25 +2001=Cabinet not found.
26 +2002=Cabinet file does not have the correct format.
27 +2003=Cabinet file has an unknown version number.
28 +2004=Cabinet file is corrupt.
29 +2005=Could not allocate enough memory to extract cabinet.
30 +2006=Unknown compression type in a cabinet folder.
31 +2007=Failure decompressing data from a cabinet file.
32 +2008=Failure writing to target file.
33 +2009=Cabinets in a set do not have the same RESERVE sizes.
34 +2010=Cabinet returned on NEXT_CABINET is incorrect.
35 +2011=Client requested abort.
src/dtf/WixToolset.Dtf.Compression.Cab/HandleManager.cs new
+76
@@ -0,0 +1,76 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 +
8 + /// <summary>
9 + /// Generic class for managing allocations of integer handles
10 + /// for objects of a certain type.
11 + /// </summary>
12 + /// <typeparam name="T">The type of objects the handles refer to.</typeparam>
13 + internal sealed class HandleManager<T> where T : class
14 + {
15 + /// <summary>
16 + /// Auto-resizing list of objects for which handles have been allocated.
17 + /// Each handle is just an index into this list. When a handle is freed,
18 + /// the list item at that index is set to null.
19 + /// </summary>
20 + private List<T> handles;
21 +
22 + /// <summary>
23 + /// Creates a new HandleManager instance.
24 + /// </summary>
25 + public HandleManager()
26 + {
27 + this.handles = new List<T>();
28 + }
29 +
30 + /// <summary>
31 + /// Gets the object of a handle, or null if the handle is invalid.
32 + /// </summary>
33 + /// <param name="handle">The integer handle previously allocated
34 + /// for the desired object.</param>
35 + /// <returns>The object for which the handle was allocated.</returns>
36 + public T this[int handle]
37 + {
38 + get
39 + {
40 + if (handle > 0 && handle <= this.handles.Count)
41 + {
42 + return this.handles[handle - 1];
43 + }
44 + else
45 + {
46 + return null;
47 + }
48 + }
49 + }
50 +
51 + /// <summary>
52 + /// Allocates a new handle for an object.
53 + /// </summary>
54 + /// <param name="obj">Object that the handle will refer to.</param>
55 + /// <returns>New handle that can be later used to retrieve the object.</returns>
56 + public int AllocHandle(T obj)
57 + {
58 + this.handles.Add(obj);
59 + int handle = this.handles.Count;
60 + return handle;
61 + }
62 +
63 + /// <summary>
64 + /// Frees a handle that was previously allocated. Afterward the handle
65 + /// will be invalid and the object it referred to can no longer retrieved.
66 + /// </summary>
67 + /// <param name="handle">Handle to be freed.</param>
68 + public void FreeHandle(int handle)
69 + {
70 + if (handle > 0 && handle <= this.handles.Count)
71 + {
72 + this.handles[handle - 1] = null;
73 + }
74 + }
75 + }
76 +}
src/dtf/WixToolset.Dtf.Compression.Cab/NativeMethods.cs new
+407
@@ -0,0 +1,407 @@
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 +namespace WixToolset.Dtf.Compression.Cab
4 +{
5 +using System;
6 +using System.Text;
7 +using System.Security;
8 +using System.Runtime.InteropServices;
9 +using System.Diagnostics.CodeAnalysis;
10 +
11 +/// <summary>
12 +/// Native DllImport methods and related structures and constants used for
13 +/// cabinet creation and extraction via cabinet.dll.
14 +/// </summary>
15 +internal static class NativeMethods
16 +{
17 + /// <summary>
18 + /// A direct import of constants, enums, structures, delegates, and functions from fci.h.
19 + /// Refer to comments in fci.h for documentation.
20 + /// </summary>
21 + internal static class FCI
22 + {
23 + internal const int MIN_DISK = 32768;
24 + internal const int MAX_DISK = Int32.MaxValue;
25 + internal const int MAX_FOLDER = 0x7FFF8000;
26 + internal const int MAX_FILENAME = 256;
27 + internal const int MAX_CABINET_NAME = 256;
28 + internal const int MAX_CAB_PATH = 256;
29 + internal const int MAX_DISK_NAME = 256;
30 +
31 + internal const int CPU_80386 = 1;
32 +
33 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
34 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
35 +
36 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode, out int err, IntPtr pv);
37 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
38 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
39 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int fileHandle, out int err, IntPtr pv);
40 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int fileHandle, int dist, int seekType, out int err, IntPtr pv);
41 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNDELETE(string path, out int err, IntPtr pv);
42 +
43 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETNEXTCABINET(IntPtr pccab, uint cbPrevCab, IntPtr pv);
44 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNFILEPLACED(IntPtr pccab, string path, long fileSize, int continuation, IntPtr pv);
45 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETOPENINFO(string path, out short date, out short time, out short pattribs, out int err, IntPtr pv);
46 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSTATUS(STATUS typeStatus, uint cb1, uint cb2, IntPtr pv);
47 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETTEMPFILE(IntPtr tempNamePtr, int tempNameSize, IntPtr pv);
48 +
49 + /// <summary>
50 + /// Error codes that can be returned by FCI.
51 + /// </summary>
52 + internal enum ERROR : int
53 + {
54 + NONE,
55 + OPEN_SRC,
56 + READ_SRC,
57 + ALLOC_FAIL,
58 + TEMP_FILE,
59 + BAD_COMPR_TYPE,
60 + CAB_FILE,
61 + USER_ABORT,
62 + MCI_FAIL,
63 + }
64 +
65 + /// <summary>
66 + /// FCI compression algorithm types and parameters.
67 + /// </summary>
68 + internal enum TCOMP : ushort
69 + {
70 + MASK_TYPE = 0x000F,
71 + TYPE_NONE = 0x0000,
72 + TYPE_MSZIP = 0x0001,
73 + TYPE_QUANTUM = 0x0002,
74 + TYPE_LZX = 0x0003,
75 + BAD = 0x000F,
76 +
77 + MASK_LZX_WINDOW = 0x1F00,
78 + LZX_WINDOW_LO = 0x0F00,
79 + LZX_WINDOW_HI = 0x1500,
80 + SHIFT_LZX_WINDOW = 0x0008,
81 +
82 + MASK_QUANTUM_LEVEL = 0x00F0,
83 + QUANTUM_LEVEL_LO = 0x0010,
84 + QUANTUM_LEVEL_HI = 0x0070,
85 + SHIFT_QUANTUM_LEVEL = 0x0004,
86 +
87 + MASK_QUANTUM_MEM = 0x1F00,
88 + QUANTUM_MEM_LO = 0x0A00,
89 + QUANTUM_MEM_HI = 0x1500,
90 + SHIFT_QUANTUM_MEM = 0x0008,
91 +
92 + MASK_RESERVED = 0xE000,
93 + }
94 +
95 + /// <summary>
96 + /// Reason for FCI status callback.
97 + /// </summary>
98 + internal enum STATUS : uint
99 + {
100 + FILE = 0,
101 + FOLDER = 1,
102 + CABINET = 2,
103 + }
104 +
105 + [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments")]
106 + [DllImport("cabinet.dll", EntryPoint = "FCICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
107 + internal static extern Handle Create(IntPtr perf, PFNFILEPLACED pfnfcifp, PFNALLOC pfna, PFNFREE pfnf, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, PFNDELETE pfndelete, PFNGETTEMPFILE pfnfcigtf, [MarshalAs(UnmanagedType.LPStruct)] CCAB pccab, IntPtr pv);
108 +
109 + [DllImport("cabinet.dll", EntryPoint = "FCIAddFile", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
110 + internal static extern int AddFile(Handle hfci, string pszSourceFile, IntPtr pszFileName, [MarshalAs(UnmanagedType.Bool)] bool fExecute, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis, PFNGETOPENINFO pfnfcigoi, TCOMP typeCompress);
111 +
112 + [DllImport("cabinet.dll", EntryPoint = "FCIFlushCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
113 + internal static extern int FlushCabinet(Handle hfci, [MarshalAs(UnmanagedType.Bool)] bool fGetNextCab, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
114 +
115 + [DllImport("cabinet.dll", EntryPoint = "FCIFlushFolder", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
116 + internal static extern int FlushFolder(Handle hfci, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
117 +
118 + [SuppressUnmanagedCodeSecurity]
119 + [DllImport("cabinet.dll", EntryPoint = "FCIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
120 + [return: MarshalAs(UnmanagedType.Bool)]
121 + internal static extern bool Destroy(IntPtr hfci);
122 +
123 + /// <summary>
124 + /// Cabinet information structure used for FCI initialization and GetNextCabinet callback.
125 + /// </summary>
126 + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
127 + internal class CCAB
128 + {
129 + internal int cb = MAX_DISK;
130 + internal int cbFolderThresh = MAX_FOLDER;
131 + internal int cbReserveCFHeader;
132 + internal int cbReserveCFFolder;
133 + internal int cbReserveCFData;
134 + internal int iCab;
135 + internal int iDisk;
136 + internal int fFailOnIncompressible;
137 + internal short setID;
138 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_DISK_NAME )] internal string szDisk = String.Empty;
139 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CABINET_NAME)] internal string szCab = String.Empty;
140 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_CAB_PATH )] internal string szCabPath = String.Empty;
141 + }
142 +
143 + /// <summary>
144 + /// Ensures that the FCI handle is safely released.
145 + /// </summary>
146 + internal class Handle : SafeHandle
147 + {
148 + /// <summary>
149 + /// Creates a new unintialized handle. The handle will be initialized
150 + /// when it is marshalled back from native code.
151 + /// </summary>
152 + internal Handle()
153 + : base(IntPtr.Zero, true)
154 + {
155 + }
156 +
157 + /// <summary>
158 + /// Checks if the handle is invalid. An FCI handle is invalid when it is zero.
159 + /// </summary>
160 + public override bool IsInvalid
161 + {
162 + get
163 + {
164 + return this.handle == IntPtr.Zero;
165 + }
166 + }
167 +
168 + /// <summary>
169 + /// Releases the handle by calling FDIDestroy().
170 + /// </summary>
171 + /// <returns>True if the release succeeded.</returns>
172 + protected override bool ReleaseHandle()
173 + {
174 + return FCI.Destroy(this.handle);
175 + }
176 + }
177 + }
178 +
179 + /// <summary>
180 + /// A direct import of constants, enums, structures, delegates, and functions from fdi.h.
181 + /// Refer to comments in fdi.h for documentation.
182 + /// </summary>
183 + internal static class FDI
184 + {
185 + internal const int MAX_DISK = Int32.MaxValue;
186 + internal const int MAX_FILENAME = 256;
187 + internal const int MAX_CABINET_NAME = 256;
188 + internal const int MAX_CAB_PATH = 256;
189 + internal const int MAX_DISK_NAME = 256;
190 +
191 + internal const int CPU_80386 = 1;
192 +
193 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
194 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
195 +
196 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode);
197 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int hf, IntPtr pv, int cb);
198 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int hf, IntPtr pv, int cb);
199 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int hf);
200 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int hf, int dist, int seektype);
201 +
202 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNNOTIFY(NOTIFICATIONTYPE fdint, NOTIFICATION fdin);
203 +
204 + /// <summary>
205 + /// Error codes that can be returned by FDI.
206 + /// </summary>
207 + internal enum ERROR : int
208 + {
209 + NONE,
210 + CABINET_NOT_FOUND,
211 + NOT_A_CABINET,
212 + UNKNOWN_CABINET_VERSION,
213 + CORRUPT_CABINET,
214 + ALLOC_FAIL,
215 + BAD_COMPR_TYPE,
216 + MDI_FAIL,
217 + TARGET_FILE,
218 + RESERVE_MISMATCH,
219 + WRONG_CABINET,
220 + USER_ABORT,
221 + }
222 +
223 + /// <summary>
224 + /// Type of notification message for the FDI Notify callback.
225 + /// </summary>
226 + internal enum NOTIFICATIONTYPE : int
227 + {
228 + CABINET_INFO,
229 + PARTIAL_FILE,
230 + COPY_FILE,
231 + CLOSE_FILE_INFO,
232 + NEXT_CABINET,
233 + ENUMERATE,
234 + }
235 +
236 + [DllImport("cabinet.dll", EntryPoint = "FDICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
237 + internal static extern Handle Create([MarshalAs(UnmanagedType.FunctionPtr)] PFNALLOC pfnalloc, [MarshalAs(UnmanagedType.FunctionPtr)] PFNFREE pfnfree, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, int cpuType, IntPtr perf);
238 +
239 + [DllImport("cabinet.dll", EntryPoint = "FDICopy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
240 + internal static extern int Copy(Handle hfdi, string pszCabinet, string pszCabPath, int flags, PFNNOTIFY pfnfdin, IntPtr pfnfdid, IntPtr pvUser);
241 +
242 + [SuppressUnmanagedCodeSecurity]
243 + [DllImport("cabinet.dll", EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
244 + [return: MarshalAs(UnmanagedType.Bool)]
245 + internal static extern bool Destroy(IntPtr hfdi);
246 +
247 + [DllImport("cabinet.dll", EntryPoint = "FDIIsCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
248 + [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", Justification="FDI file handles definitely remain 4 bytes on 64bit platforms.")]
249 + internal static extern int IsCabinet(Handle hfdi, int hf, out CABINFO pfdici);
250 +
251 + /// <summary>
252 + /// Cabinet information structure filled in by FDI IsCabinet.
253 + /// </summary>
254 + [StructLayout(LayoutKind.Sequential)]
255 + internal struct CABINFO
256 + {
257 + internal int cbCabinet;
258 + internal short cFolders;
259 + internal short cFiles;
260 + internal short setID;
261 + internal short iCabinet;
262 + internal int fReserve;
263 + internal int hasprev;
264 + internal int hasnext;
265 + }
266 +
267 + /// <summary>
268 + /// Cabinet notification details passed to the FDI Notify callback.
269 + /// </summary>
270 + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
271 + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
272 + internal class NOTIFICATION
273 + {
274 + internal int cb;
275 + internal IntPtr psz1;
276 + internal IntPtr psz2;
277 + internal IntPtr psz3;
278 + internal IntPtr pv;
279 +
280 + internal IntPtr hf_ptr;
281 +
282 + internal short date;
283 + internal short time;
284 + internal short attribs;
285 + internal short setID;
286 + internal short iCabinet;
287 + internal short iFolder;
288 + internal int fdie;
289 +
290 + // Unlike all the other file handles in FCI/FDI, this one is
291 + // actually pointer-sized. Use a property to pretend it isn't.
292 + internal int hf
293 + {
294 + get { return (int) this.hf_ptr; }
295 + }
296 + }
297 +
298 + /// <summary>
299 + /// Ensures that the FDI handle is safely released.
300 + /// </summary>
301 + internal class Handle : SafeHandle
302 + {
303 + /// <summary>
304 + /// Creates a new unintialized handle. The handle will be initialized
305 + /// when it is marshalled back from native code.
306 + /// </summary>
307 + internal Handle()
308 + : base(IntPtr.Zero, true)
309 + {
310 + }
311 +
312 + /// <summary>
313 + /// Checks if the handle is invalid. An FDI handle is invalid when it is zero.
314 + /// </summary>
315 + public override bool IsInvalid
316 + {
317 + get
318 + {
319 + return this.handle == IntPtr.Zero;
320 + }
321 + }
322 +
323 + /// <summary>
324 + /// Releases the handle by calling FDIDestroy().
325 + /// </summary>
326 + /// <returns>True if the release succeeded.</returns>
327 + protected override bool ReleaseHandle()
328 + {
329 + return FDI.Destroy(this.handle);
330 + }
331 + }
332 + }
333 +
334 + /// <summary>
335 + /// Error info structure for FCI and FDI.
336 + /// </summary>
337 + /// <remarks>Before being passed to FCI or FDI, this structure is
338 + /// pinned in memory via a GCHandle. The pinning is necessary
339 + /// to be able to read the results, since the ERF structure doesn't
340 + /// get marshalled back out after an error.</remarks>
341 + [StructLayout(LayoutKind.Sequential)]
342 + internal class ERF
343 + {
344 + private int erfOper;
345 + private int erfType;
346 + private int fError;
347 +
348 + /// <summary>
349 + /// Gets or sets the cabinet error code.
350 + /// </summary>
351 + internal int Oper
352 + {
353 + get
354 + {
355 + return this.erfOper;
356 + }
357 +
358 + set
359 + {
360 + this.erfOper = value;
361 + }
362 + }
363 +
364 + /// <summary>
365 + /// Gets or sets the Win32 error code.
366 + /// </summary>
367 + internal int Type
368 + {
369 + get
370 + {
371 + return this.erfType;
372 + }
373 +
374 + set
375 + {
376 + this.erfType = value;
377 + }
378 + }
379 +
380 + /// <summary>
381 + /// GCHandle doesn't like the bool type, so use an int underneath.
382 + /// </summary>
383 + internal bool Error
384 + {
385 + get
386 + {
387 + return this.fError != 0;
388 + }
389 +
390 + set
391 + {
392 + this.fError = value ? 1 : 0;
393 + }
394 + }
395 +
396 + /// <summary>
397 + /// Clears the error information.
398 + /// </summary>
399 + internal void Clear()
400 + {
401 + this.Oper = 0;
402 + this.Type = 0;
403 + this.Error = false;
404 + }
405 + }
406 +}
407 +}
src/dtf/WixToolset.Dtf.Compression.Cab/WixToolset.Dtf.Compression.Cab.csproj new
+26
@@ -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 + <RootNamespace>WixToolset.Dtf.Compression.Cab</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.Compression.Cab</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Managed libraries for cabinet archive packing and unpacking</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <None Include="Errors.txt" />
15 + <EmbeddedResource Include="Errors.resources" />
16 + </ItemGroup>
17 +
18 + <ItemGroup>
19 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
20 + </ItemGroup>
21 +
22 + <ItemGroup>
23 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 + </ItemGroup>
26 +</Project>
src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Diagnostics.CodeAnalysis;
4 +
5 +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Zip")]
src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs new
+157
@@ -0,0 +1,157 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + /// <summary>
9 + /// Used to trick a DeflateStream into reading from or writing to
10 + /// a series of (chunked) streams instead of a single steream.
11 + /// </summary>
12 + internal class ConcatStream : Stream
13 + {
14 + private Stream source;
15 + private long position;
16 + private long length;
17 + private Action<ConcatStream> nextStreamHandler;
18 +
19 + public ConcatStream(Action<ConcatStream> nextStreamHandler)
20 + {
21 + if (nextStreamHandler == null)
22 + {
23 + throw new ArgumentNullException("nextStreamHandler");
24 + }
25 +
26 + this.nextStreamHandler = nextStreamHandler;
27 + this.length = Int64.MaxValue;
28 + }
29 +
30 + public Stream Source
31 + {
32 + get { return this.source; }
33 + set { this.source = value; }
34 + }
35 +
36 + public override bool CanRead
37 + {
38 + get { return true; }
39 + }
40 +
41 + public override bool CanWrite
42 + {
43 + get { return true; }
44 + }
45 +
46 + public override bool CanSeek
47 + {
48 + get { return false; }
49 + }
50 +
51 + public override long Length
52 + {
53 + get
54 + {
55 + return this.length;
56 + }
57 + }
58 +
59 + public override long Position
60 + {
61 + get { return this.position; }
62 + set { throw new NotSupportedException(); }
63 + }
64 +
65 + public override int Read(byte[] buffer, int offset, int count)
66 + {
67 + if (this.source == null)
68 + {
69 + this.nextStreamHandler(this);
70 + }
71 +
72 + count = (int) Math.Min(count, this.length - this.position);
73 +
74 + int bytesRemaining = count;
75 + while (bytesRemaining > 0)
76 + {
77 + if (this.source == null)
78 + {
79 + throw new InvalidOperationException();
80 + }
81 +
82 + int partialCount = (int) Math.Min(bytesRemaining,
83 + this.source.Length - this.source.Position);
84 +
85 + if (partialCount == 0)
86 + {
87 + this.nextStreamHandler(this);
88 + continue;
89 + }
90 +
91 + partialCount = this.source.Read(
92 + buffer, offset + count - bytesRemaining, partialCount);
93 + bytesRemaining -= partialCount;
94 + this.position += partialCount;
95 + }
96 +
97 + return count;
98 + }
99 +
100 + public override void Write(byte[] buffer, int offset, int count)
101 + {
102 + if (this.source == null)
103 + {
104 + this.nextStreamHandler(this);
105 + }
106 +
107 + int bytesRemaining = count;
108 + while (bytesRemaining > 0)
109 + {
110 + if (this.source == null)
111 + {
112 + throw new InvalidOperationException();
113 + }
114 +
115 + int partialCount = (int) Math.Min(bytesRemaining,
116 + Math.Max(0, this.length - this.source.Position));
117 +
118 + if (partialCount == 0)
119 + {
120 + this.nextStreamHandler(this);
121 + continue;
122 + }
123 +
124 + this.source.Write(
125 + buffer, offset + count - bytesRemaining, partialCount);
126 + bytesRemaining -= partialCount;
127 + this.position += partialCount;
128 + }
129 + }
130 +
131 + public override void Flush()
132 + {
133 + if (this.source != null)
134 + {
135 + this.source.Flush();
136 + }
137 + }
138 +
139 + public override long Seek(long offset, SeekOrigin origin)
140 + {
141 + throw new NotSupportedException();
142 + }
143 +
144 + public override void SetLength(long value)
145 + {
146 + this.length = value;
147 + }
148 +
149 + public override void Close()
150 + {
151 + if (this.source != null)
152 + {
153 + this.source.Close();
154 + }
155 + }
156 + }
157 +}
src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs new
+250
@@ -0,0 +1,250 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Diagnostics.CodeAnalysis;
8 +
9 + /// <summary>
10 + /// Wraps a source stream and calcaluates a CRC over all bytes that are read or written.
11 + /// </summary>
12 + /// <remarks>
13 + /// The CRC algorithm matches that used in the standard ZIP file format.
14 + /// </remarks>
15 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
16 + public class CrcStream : Stream
17 + {
18 + private Stream source;
19 + private uint crc;
20 +
21 + /// <summary>
22 + /// Creates a new CrcStream instance from a source stream.
23 + /// </summary>
24 + /// <param name="source">Underlying stream where bytes will be read from or written to.</param>
25 + public CrcStream(Stream source)
26 + {
27 + this.source = source;
28 + }
29 +
30 + /// <summary>
31 + /// Gets the current CRC over all bytes that have been read or written
32 + /// since this instance was created.
33 + /// </summary>
34 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
35 + public uint Crc
36 + {
37 + get
38 + {
39 + return this.crc;
40 + }
41 + }
42 +
43 + /// <summary>
44 + /// Gets the underlying stream that this stream reads from or writes to.
45 + /// </summary>
46 + public Stream Source
47 + {
48 + get
49 + {
50 + return this.source;
51 + }
52 + }
53 +
54 + /// <summary>
55 + /// Gets a value indicating whether the source stream supports reading.
56 + /// </summary>
57 + /// <value>true if the stream supports reading; otherwise, false.</value>
58 + public override bool CanRead
59 + {
60 + get
61 + {
62 + return this.source.CanRead;
63 + }
64 + }
65 +
66 + /// <summary>
67 + /// Gets a value indicating whether the source stream supports writing.
68 + /// </summary>
69 + /// <value>true if the stream supports writing; otherwise, false.</value>
70 + public override bool CanWrite
71 + {
72 + get
73 + {
74 + return this.source.CanWrite;
75 + }
76 + }
77 +
78 + /// <summary>
79 + /// Gets a value indicating whether the source stream supports seeking.
80 + /// </summary>
81 + /// <value>true if the stream supports seeking; otherwise, false.</value>
82 + public override bool CanSeek
83 + {
84 + get
85 + {
86 + return this.source.CanSeek;
87 + }
88 + }
89 +
90 + /// <summary>
91 + /// Gets the length of the source stream.
92 + /// </summary>
93 + public override long Length
94 + {
95 + get
96 + {
97 + return this.source.Length;
98 + }
99 + }
100 +
101 + /// <summary>
102 + /// Gets or sets the position of the source stream.
103 + /// </summary>
104 + public override long Position
105 + {
106 + get
107 + {
108 + return this.source.Position;
109 + }
110 +
111 + set
112 + {
113 + this.source.Position = value;
114 + }
115 + }
116 +
117 + /// <summary>
118 + /// Sets the position within the source stream.
119 + /// </summary>
120 + /// <param name="offset">A byte offset relative to the origin parameter.</param>
121 + /// <param name="origin">A value of type SeekOrigin indicating
122 + /// the reference point used to obtain the new position.</param>
123 + /// <returns>The new position within the source stream.</returns>
124 + /// <remarks>
125 + /// Note the CRC is only calculated over bytes that are actually read or
126 + /// written, so any bytes skipped by seeking will not contribute to the CRC.
127 + /// </remarks>
128 + public override long Seek(long offset, SeekOrigin origin)
129 + {
130 + return this.source.Seek(offset, origin);
131 + }
132 +
133 + /// <summary>
134 + /// Sets the length of the source stream.
135 + /// </summary>
136 + /// <param name="value">The desired length of the
137 + /// stream in bytes.</param>
138 + public override void SetLength(long value)
139 + {
140 + this.source.SetLength(value);
141 + }
142 +
143 + /// <summary>
144 + /// Reads a sequence of bytes from the source stream and advances
145 + /// the position within the stream by the number of bytes read.
146 + /// </summary>
147 + /// <param name="buffer">An array of bytes. When this method returns, the buffer
148 + /// contains the specified byte array with the values between offset and
149 + /// (offset + count - 1) replaced by the bytes read from the current source.</param>
150 + /// <param name="offset">The zero-based byte offset in buffer at which to begin
151 + /// storing the data read from the current stream.</param>
152 + /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
153 + /// <returns>The total number of bytes read into the buffer. This can be less
154 + /// than the number of bytes requested if that many bytes are not currently available,
155 + /// or zero (0) if the end of the stream has been reached.</returns>
156 + public override int Read(byte[] buffer, int offset, int count)
157 + {
158 + count = this.source.Read(buffer, offset, count);
159 + this.UpdateCrc(buffer, offset, count);
160 + return count;
161 + }
162 +
163 + /// <summary>
164 + /// Writes a sequence of bytes to the source stream and advances the
165 + /// current position within this stream by the number of bytes written.
166 + /// </summary>
167 + /// <param name="buffer">An array of bytes. This method copies count
168 + /// bytes from buffer to the current stream.</param>
169 + /// <param name="offset">The zero-based byte offset in buffer at which
170 + /// to begin copying bytes to the current stream.</param>
171 + /// <param name="count">The number of bytes to be written to the
172 + /// current stream.</param>
173 + public override void Write(byte[] buffer, int offset, int count)
174 + {
175 + this.source.Write(buffer, offset, count);
176 + this.UpdateCrc(buffer, offset, count);
177 + }
178 +
179 + /// <summary>
180 + /// Flushes the source stream.
181 + /// </summary>
182 + public override void Flush()
183 + {
184 + this.source.Flush();
185 + }
186 +
187 + /// <summary>
188 + /// Closes the underlying stream.
189 + /// </summary>
190 + public override void Close()
191 + {
192 + this.source.Close();
193 + base.Close();
194 + }
195 +
196 + /// <summary>
197 + /// Updates the CRC with a range of bytes that were read or written.
198 + /// </summary>
199 + private void UpdateCrc(byte[] buffer, int offset, int count)
200 + {
201 + this.crc = ~this.crc;
202 + for( ; count > 0; count--, offset++)
203 + {
204 + this.crc = (this.crc >> 8) ^
205 + CrcStream.crcTable[(this.crc & 0xFF) ^ buffer[offset]];
206 + }
207 + this.crc = ~this.crc;
208 + }
209 +
210 + private static uint[] crcTable = MakeCrcTable();
211 +
212 + /// <summary>
213 + /// Computes a table that speeds up calculation of the CRC.
214 + /// </summary>
215 + private static uint[] MakeCrcTable()
216 + {
217 + const uint poly = 0x04C11DB7u;
218 + uint[] crcTable = new uint[256];
219 + for(uint n = 0; n < 256; n++)
220 + {
221 + uint c = CrcStream.Reflect(n, 8);
222 + c = c << 24;
223 + for(uint k = 0; k < 8; k++)
224 + {
225 + c = (c << 1) ^ ((c & 0x80000000u) != 0 ? poly : 0);
226 + }
227 + crcTable[n] = CrcStream.Reflect(c, 32);
228 + }
229 + return crcTable;
230 + }
231 +
232 + /// <summary>
233 + /// Reflects the ordering of certain number of bits. For exmample when reflecting
234 + /// one byte, bit one is swapped with bit eight, bit two with bit seven, etc.
235 + /// </summary>
236 + private static uint Reflect(uint value, int bits)
237 + {
238 + for (int i = 0; i < bits / 2; i++)
239 + {
240 + uint leftBit = 1u << (bits - 1 - i);
241 + uint rightBit = 1u << i;
242 + if (((value & leftBit) != 0) != ((value & rightBit) != 0))
243 + {
244 + value ^= leftBit | rightBit;
245 + }
246 + }
247 + return value;
248 + }
249 + }
250 +}
src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj new
+21
@@ -0,0 +1,21 @@
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 + <RootNamespace>WixToolset.Dtf.Compression.Zip</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.Compression.Zip</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Managed libraries for zip archive packing and unpacking</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
15 + </ItemGroup>
16 +
17 + <ItemGroup>
18 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 + </ItemGroup>
21 +</Project>
src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs new
+80
@@ -0,0 +1,80 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System.Diagnostics.CodeAnalysis;
6 +
7 + /// <summary>
8 + /// Identifies the compression method or &quot;algorithm&quot;
9 + /// used for a single file within a zip archive.
10 + /// </summary>
11 + /// <remarks>
12 + /// Proprietary zip implementations may define additional compression
13 + /// methods outside of those included here.
14 + /// </remarks>
15 + public enum ZipCompressionMethod
16 + {
17 + /// <summary>
18 + /// The file is stored (no compression)
19 + /// </summary>
20 + Store = 0,
21 +
22 + /// <summary>
23 + /// The file is Shrunk
24 + /// </summary>
25 + Shrink = 1,
26 +
27 + /// <summary>
28 + /// The file is Reduced with compression factor 1
29 + /// </summary>
30 + Reduce1 = 2,
31 +
32 + /// <summary>
33 + /// The file is Reduced with compression factor 2
34 + /// </summary>
35 + Reduce2 = 3,
36 +
37 + /// <summary>
38 + /// The file is Reduced with compression factor 3
39 + /// </summary>
40 + Reduce3 = 4,
41 +
42 + /// <summary>
43 + /// The file is Reduced with compression factor 4
44 + /// </summary>
45 + Reduce4 = 5,
46 +
47 + /// <summary>
48 + /// The file is Imploded
49 + /// </summary>
50 + Implode = 6,
51 +
52 + /// <summary>
53 + /// The file is Deflated;
54 + /// the most common and widely-compatible form of zip compression.
55 + /// </summary>
56 + Deflate = 8,
57 +
58 + /// <summary>
59 + /// The file is Deflated using the enhanced Deflate64 method.
60 + /// </summary>
61 + Deflate64 = 9,
62 +
63 + /// <summary>
64 + /// The file is compressed using the BZIP2 algorithm.
65 + /// </summary>
66 + BZip2 = 12,
67 +
68 + /// <summary>
69 + /// The file is compressed using the LZMA algorithm.
70 + /// </summary>
71 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lzma")]
72 + Lzma = 14,
73 +
74 + /// <summary>
75 + /// The file is compressed using the PPMd algorithm.
76 + /// </summary>
77 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppmd")]
78 + Ppmd = 98
79 + }
80 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs new
+478
@@ -0,0 +1,478 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.IO.Compression;
8 + using System.Collections.Generic;
9 + using System.Reflection;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + /// <summary>
13 + /// Engine capable of packing and unpacking archives in the zip format.
14 + /// </summary>
15 + public partial class ZipEngine : CompressionEngine
16 + {
17 + private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
18 + compressionStreamCreators;
19 + private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
20 + decompressionStreamCreators;
21 +
22 + private static void InitCompressionStreamCreators()
23 + {
24 + if (ZipEngine.compressionStreamCreators == null)
25 + {
26 + ZipEngine.compressionStreamCreators = new
27 + Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
28 + ZipEngine.decompressionStreamCreators = new
29 + Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
30 +
31 + ZipEngine.RegisterCompressionStreamCreator(
32 + ZipCompressionMethod.Store,
33 + CompressionMode.Compress,
34 + delegate(Stream stream) {
35 + return stream;
36 + });
37 + ZipEngine.RegisterCompressionStreamCreator(
38 + ZipCompressionMethod.Deflate,
39 + CompressionMode.Compress,
40 + delegate(Stream stream) {
41 + return new DeflateStream(stream, CompressionMode.Compress, true);
42 + });
43 + ZipEngine.RegisterCompressionStreamCreator(
44 + ZipCompressionMethod.Store,
45 + CompressionMode.Decompress,
46 + delegate(Stream stream) {
47 + return stream;
48 + });
49 + ZipEngine.RegisterCompressionStreamCreator(
50 + ZipCompressionMethod.Deflate,
51 + CompressionMode.Decompress,
52 + delegate(Stream stream) {
53 + return new DeflateStream(stream, CompressionMode.Decompress, true);
54 + });
55 + }
56 + }
57 +
58 + /// <summary>
59 + /// Registers a delegate that can create a warpper stream for
60 + /// compressing or uncompressing the data of a source stream.
61 + /// </summary>
62 + /// <param name="compressionMethod">Compression method being registered.</param>
63 + /// <param name="compressionMode">Indicates registration for ether
64 + /// compress or decompress mode.</param>
65 + /// <param name="creator">Delegate being registered.</param>
66 + /// <remarks>
67 + /// For compression, the delegate accepts a stream that writes to the archive
68 + /// and returns a wrapper stream that compresses bytes as they are written.
69 + /// For decompression, the delegate accepts a stream that reads from the archive
70 + /// and returns a wrapper stream that decompresses bytes as they are read.
71 + /// This wrapper stream model follows the design used by
72 + /// System.IO.Compression.DeflateStream, and indeed that class is used
73 + /// to implement the Deflate compression method by default.
74 + /// <para>To unregister a delegate, call this method again and pass
75 + /// null for the delegate parameter.</para>
76 + /// </remarks>
77 + /// <example>
78 + /// When the ZipEngine class is initialized, the Deflate compression method
79 + /// is automatically registered like this:
80 + /// <code>
81 + /// ZipEngine.RegisterCompressionStreamCreator(
82 + /// ZipCompressionMethod.Deflate,
83 + /// CompressionMode.Compress,
84 + /// delegate(Stream stream) {
85 + /// return new DeflateStream(stream, CompressionMode.Compress, true);
86 + /// });
87 + /// ZipEngine.RegisterCompressionStreamCreator(
88 + /// ZipCompressionMethod.Deflate,
89 + /// CompressionMode.Decompress,
90 + /// delegate(Stream stream) {
91 + /// return new DeflateStream(stream, CompressionMode.Decompress, true);
92 + /// });
93 + /// </code></example>
94 + public static void RegisterCompressionStreamCreator(
95 + ZipCompressionMethod compressionMethod,
96 + CompressionMode compressionMode,
97 + Converter<Stream, Stream> creator)
98 + {
99 + ZipEngine.InitCompressionStreamCreators();
100 + if (compressionMode == CompressionMode.Compress)
101 + {
102 + ZipEngine.compressionStreamCreators[compressionMethod] = creator;
103 + }
104 + else
105 + {
106 + ZipEngine.decompressionStreamCreators[compressionMethod] = creator;
107 + }
108 + }
109 +
110 + // Progress data
111 + private string currentFileName;
112 + private int currentFileNumber;
113 + private int totalFiles;
114 + private long currentFileBytesProcessed;
115 + private long currentFileTotalBytes;
116 + private string mainArchiveName;
117 + private string currentArchiveName;
118 + private short currentArchiveNumber;
119 + private short totalArchives;
120 + private long currentArchiveBytesProcessed;
121 + private long currentArchiveTotalBytes;
122 + private long fileBytesProcessed;
123 + private long totalFileBytes;
124 + private string comment;
125 +
126 + /// <summary>
127 + /// Creates a new instance of the zip engine.
128 + /// </summary>
129 + public ZipEngine()
130 + : base()
131 + {
132 + ZipEngine.InitCompressionStreamCreators();
133 + }
134 +
135 + /// <summary>
136 + /// Gets the comment from the last-examined archive,
137 + /// or sets the comment to be added to any created archives.
138 + /// </summary>
139 + public string ArchiveComment
140 + {
141 + get
142 + {
143 + return this.comment;
144 + }
145 + set
146 + {
147 + this.comment = value;
148 + }
149 + }
150 +
151 + /// <summary>
152 + /// Checks whether a Stream begins with a header that indicates
153 + /// it is a valid archive file.
154 + /// </summary>
155 + /// <param name="stream">Stream for reading the archive file.</param>
156 + /// <returns>True if the stream is a valid zip archive
157 + /// (with no offset); false otherwise.</returns>
158 + public override bool IsArchive(Stream stream)
159 + {
160 + if (stream == null)
161 + {
162 + throw new ArgumentNullException("stream");
163 + }
164 +
165 + if (stream.Length - stream.Position < 4)
166 + {
167 + return false;
168 + }
169 +
170 + BinaryReader reader = new BinaryReader(stream);
171 + uint sig = reader.ReadUInt32();
172 + switch (sig)
173 + {
174 + case ZipFileHeader.LFHSIG:
175 + case ZipEndOfCentralDirectory.EOCDSIG:
176 + case ZipEndOfCentralDirectory.EOCD64SIG:
177 + case ZipFileHeader.SPANSIG:
178 + case ZipFileHeader.SPANSIG2:
179 + return true;
180 + default:
181 + return false;
182 + }
183 + }
184 +
185 + /// <summary>
186 + /// Gets the offset of an archive that is positioned 0 or more bytes
187 + /// from the start of the Stream.
188 + /// </summary>
189 + /// <param name="stream">A stream for reading the archive.</param>
190 + /// <returns>The offset in bytes of the archive,
191 + /// or -1 if no archive is found in the Stream.</returns>
192 + /// <remarks>The archive must begin on a 4-byte boundary.</remarks>
193 + public override long FindArchiveOffset(Stream stream)
194 + {
195 + long offset = base.FindArchiveOffset(stream);
196 + if (offset > 0)
197 + {
198 + // Some self-extract packages include the exe stub in file offset calculations.
199 + // Check the first header directory offset to decide whether the entire
200 + // archive needs to be offset or not.
201 +
202 + ZipEndOfCentralDirectory eocd = this.GetEOCD(null, stream);
203 + if (eocd != null && eocd.totalEntries > 0)
204 + {
205 + stream.Seek(eocd.dirOffset, SeekOrigin.Begin);
206 +
207 + ZipFileHeader header = new ZipFileHeader();
208 + if (header.Read(stream, true) && header.localHeaderOffset < stream.Length)
209 + {
210 + stream.Seek(header.localHeaderOffset, SeekOrigin.Begin);
211 + if (header.Read(stream, false))
212 + {
213 + return 0;
214 + }
215 + }
216 + }
217 + }
218 +
219 + return offset;
220 + }
221 +
222 + /// <summary>
223 + /// Gets information about files in a zip archive or archive chain.
224 + /// </summary>
225 + /// <param name="streamContext">A context interface to handle opening
226 + /// and closing of archive and file streams.</param>
227 + /// <param name="fileFilter">A predicate that can determine
228 + /// which files to process, optional.</param>
229 + /// <returns>Information about files in the archive stream.</returns>
230 + /// <exception cref="ArchiveException">The archive provided
231 + /// by the stream context is not valid.</exception>
232 + /// <remarks>
233 + /// The <paramref name="fileFilter"/> predicate takes an internal file
234 + /// path and returns true to include the file or false to exclude it.
235 + /// </remarks>
236 + public override IList<ArchiveFileInfo> GetFileInfo(
237 + IUnpackStreamContext streamContext,
238 + Predicate<string> fileFilter)
239 + {
240 + if (streamContext == null)
241 + {
242 + throw new ArgumentNullException("streamContext");
243 + }
244 +
245 + lock (this)
246 + {
247 + IList<ZipFileHeader> headers = this.GetCentralDirectory(streamContext);
248 + if (headers == null)
249 + {
250 + throw new ZipException("Zip central directory not found.");
251 + }
252 +
253 + List<ArchiveFileInfo> files = new List<ArchiveFileInfo>(headers.Count);
254 + foreach (ZipFileHeader header in headers)
255 + {
256 + if (!header.IsDirectory &&
257 + (fileFilter == null || fileFilter(header.fileName)))
258 + {
259 + files.Add(header.ToZipFileInfo());
260 + }
261 + }
262 +
263 + return files.AsReadOnly();
264 + }
265 + }
266 +
267 + /// <summary>
268 + /// Reads all the file headers from the central directory in the main archive.
269 + /// </summary>
270 + private IList<ZipFileHeader> GetCentralDirectory(IUnpackStreamContext streamContext)
271 + {
272 + Stream archiveStream = null;
273 + this.currentArchiveNumber = 0;
274 + try
275 + {
276 + List<ZipFileHeader> headers = new List<ZipFileHeader>();
277 + archiveStream = this.OpenArchive(streamContext, 0);
278 +
279 + ZipEndOfCentralDirectory eocd = this.GetEOCD(streamContext, archiveStream);
280 + if (eocd == null)
281 + {
282 + return null;
283 + }
284 + else if (eocd.totalEntries == 0)
285 + {
286 + return headers;
287 + }
288 +
289 + headers.Capacity = (int) eocd.totalEntries;
290 +
291 + if (eocd.dirOffset > archiveStream.Length - ZipFileHeader.CFH_FIXEDSIZE)
292 + {
293 + streamContext.CloseArchiveReadStream(
294 + this.currentArchiveNumber, String.Empty, archiveStream);
295 + archiveStream = null;
296 + }
297 + else
298 + {
299 + archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
300 + uint sig = new BinaryReader(archiveStream).ReadUInt32();
301 + if (sig != ZipFileHeader.CFHSIG)
302 + {
303 + streamContext.CloseArchiveReadStream(
304 + this.currentArchiveNumber, String.Empty, archiveStream);
305 + archiveStream = null;
306 + }
307 + }
308 +
309 + if (archiveStream == null)
310 + {
311 + this.currentArchiveNumber = (short) (eocd.dirStartDiskNumber + 1);
312 + archiveStream = streamContext.OpenArchiveReadStream(
313 + this.currentArchiveNumber, String.Empty, this);
314 +
315 + if (archiveStream == null)
316 + {
317 + return null;
318 + }
319 + }
320 +
321 + archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
322 +
323 + while (headers.Count < eocd.totalEntries)
324 + {
325 + ZipFileHeader header = new ZipFileHeader();
326 + if (!header.Read(archiveStream, true))
327 + {
328 + throw new ZipException(
329 + "Missing or invalid central directory file header");
330 + }
331 +
332 + headers.Add(header);
333 +
334 + if (headers.Count < eocd.totalEntries &&
335 + archiveStream.Position == archiveStream.Length)
336 + {
337 + streamContext.CloseArchiveReadStream(
338 + this.currentArchiveNumber, String.Empty, archiveStream);
339 + this.currentArchiveNumber++;
340 + archiveStream = streamContext.OpenArchiveReadStream(
341 + this.currentArchiveNumber, String.Empty, this);
342 + if (archiveStream == null)
343 + {
344 + this.currentArchiveNumber = 0;
345 + archiveStream = streamContext.OpenArchiveReadStream(
346 + this.currentArchiveNumber, String.Empty, this);
347 + }
348 + }
349 + }
350 +
351 + return headers;
352 + }
353 + finally
354 + {
355 + if (archiveStream != null)
356 + {
357 + streamContext.CloseArchiveReadStream(
358 + this.currentArchiveNumber, String.Empty, archiveStream);
359 + }
360 + }
361 + }
362 +
363 + /// <summary>
364 + /// Locates and reads the end of central directory record near the
365 + /// end of the archive.
366 + /// </summary>
367 + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
368 + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "streamContext")]
369 + private ZipEndOfCentralDirectory GetEOCD(
370 + IUnpackStreamContext streamContext, Stream archiveStream)
371 + {
372 + BinaryReader reader = new BinaryReader(archiveStream);
373 + long offset = archiveStream.Length
374 + - ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE;
375 + while (offset >= 0)
376 + {
377 + archiveStream.Seek(offset, SeekOrigin.Begin);
378 +
379 + uint sig = reader.ReadUInt32();
380 + if (sig == ZipEndOfCentralDirectory.EOCDSIG)
381 + {
382 + break;
383 + }
384 +
385 + offset--;
386 + }
387 +
388 + if (offset < 0)
389 + {
390 + return null;
391 + }
392 +
393 + ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
394 + archiveStream.Seek(offset, SeekOrigin.Begin);
395 + if (!eocd.Read(archiveStream))
396 + {
397 + throw new ZipException("Invalid end of central directory record");
398 + }
399 +
400 + if (eocd.dirOffset == (long) UInt32.MaxValue)
401 + {
402 + string saveComment = eocd.comment;
403 +
404 + archiveStream.Seek(
405 + offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE,
406 + SeekOrigin.Begin);
407 +
408 + Zip64EndOfCentralDirectoryLocator eocdl =
409 + new Zip64EndOfCentralDirectoryLocator();
410 + if (!eocdl.Read(archiveStream))
411 + {
412 + throw new ZipException("Missing or invalid end of " +
413 + "central directory record locator");
414 + }
415 +
416 + if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1)
417 + {
418 + // ZIP64 eocd is entirely in current stream.
419 + archiveStream.Seek(eocdl.dirOffset, SeekOrigin.Begin);
420 + if (!eocd.Read(archiveStream))
421 + {
422 + throw new ZipException("Missing or invalid ZIP64 end of " +
423 + "central directory record");
424 + }
425 + }
426 + else if (streamContext == null)
427 + {
428 + return null;
429 + }
430 + else
431 + {
432 + // TODO: handle EOCD64 spanning archives!
433 + throw new NotImplementedException("Zip implementation does not " +
434 + "handle end of central directory record that spans archives.");
435 + }
436 +
437 + eocd.comment = saveComment;
438 + }
439 +
440 + return eocd;
441 + }
442 +
443 + private void ResetProgressData()
444 + {
445 + this.currentFileName = null;
446 + this.currentFileNumber = 0;
447 + this.totalFiles = 0;
448 + this.currentFileBytesProcessed = 0;
449 + this.currentFileTotalBytes = 0;
450 + this.currentArchiveName = null;
451 + this.currentArchiveNumber = 0;
452 + this.totalArchives = 0;
453 + this.currentArchiveBytesProcessed = 0;
454 + this.currentArchiveTotalBytes = 0;
455 + this.fileBytesProcessed = 0;
456 + this.totalFileBytes = 0;
457 + }
458 +
459 + private void OnProgress(ArchiveProgressType progressType)
460 + {
461 + ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
462 + progressType,
463 + this.currentFileName,
464 + this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
465 + this.totalFiles,
466 + this.currentFileBytesProcessed,
467 + this.currentFileTotalBytes,
468 + this.currentArchiveName,
469 + this.currentArchiveNumber,
470 + this.totalArchives,
471 + this.currentArchiveBytesProcessed,
472 + this.currentArchiveTotalBytes,
473 + this.fileBytesProcessed,
474 + this.totalFileBytes);
475 + this.OnProgress(e);
476 + }
477 + }
478 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs new
+60
@@ -0,0 +1,60 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Resources;
8 + using System.Globalization;
9 + using System.Runtime.Serialization;
10 +
11 + /// <summary>
12 + /// Exception class for zip operations.
13 + /// </summary>
14 + [Serializable]
15 + public class ZipException : ArchiveException
16 + {
17 + /// <summary>
18 + /// Creates a new ZipException with a specified error message and a reference to the
19 + /// inner exception that is the cause of this exception.
20 + /// </summary>
21 + /// <param name="message">The message that describes the error.</param>
22 + /// <param name="innerException">The exception that is the cause of the current exception. If the
23 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
24 + /// is raised in a catch block that handles the inner exception.</param>
25 + public ZipException(string message, Exception innerException)
26 + : base(message, innerException) { }
27 +
28 + /// <summary>
29 + /// Creates a new ZipException with a specified error message.
30 + /// </summary>
31 + /// <param name="message">The message that describes the error.</param>
32 + public ZipException(string message)
33 + : this(message, null) { }
34 +
35 + /// <summary>
36 + /// Creates a new ZipException.
37 + /// </summary>
38 + public ZipException()
39 + : this(null, null) { }
40 +
41 + /// <summary>
42 + /// Initializes a new instance of the ZipException class with serialized data.
43 + /// </summary>
44 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
45 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
46 + protected ZipException(SerializationInfo info, StreamingContext context) : base(info, context)
47 + {
48 + }
49 +
50 + /// <summary>
51 + /// Sets the SerializationInfo with information about the exception.
52 + /// </summary>
53 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
54 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
55 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
56 + {
57 + base.GetObjectData(info, context);
58 + }
59 + }
60 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs new
+104
@@ -0,0 +1,104 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Runtime.Serialization;
8 +
9 + /// <summary>
10 + /// Object representing a compressed file within a zip package; provides operations for getting
11 + /// the file properties and extracting the file.
12 + /// </summary>
13 + [Serializable]
14 + public class ZipFileInfo : ArchiveFileInfo
15 + {
16 + private long compressedLength;
17 + private ZipCompressionMethod compressionMethod;
18 +
19 + /// <summary>
20 + /// Creates a new ZipFileInfo object representing a file within a zip in a specified path.
21 + /// </summary>
22 + /// <param name="zipInfo">An object representing the zip archive containing the file.</param>
23 + /// <param name="filePath">The path to the file within the zip archive. Usually, this is a simple file
24 + /// name, but if the zip archive contains a directory structure this may include the directory.</param>
25 + public ZipFileInfo(ZipInfo zipInfo, string filePath)
26 + : base(zipInfo, filePath)
27 + {
28 + if (zipInfo == null)
29 + {
30 + throw new ArgumentNullException("zipInfo");
31 + }
32 + }
33 +
34 + /// <summary>
35 + /// Creates a new ZipFileInfo object with all parameters specified,
36 + /// used internally when reading the metadata out of a zip archive.
37 + /// </summary>
38 + /// <param name="filePath">The internal path and name of the file in the zip archive.</param>
39 + /// <param name="zipNumber">The zip archive number where the file starts.</param>
40 + /// <param name="attributes">The stored attributes of the file.</param>
41 + /// <param name="lastWriteTime">The stored last write time of the file.</param>
42 + /// <param name="length">The uncompressed size of the file.</param>
43 + /// <param name="compressedLength">The compressed size of the file.</param>
44 + /// <param name="compressionMethod">Compression algorithm used for this file.</param>
45 + internal ZipFileInfo(
46 + string filePath,
47 + int zipNumber,
48 + FileAttributes attributes,
49 + DateTime lastWriteTime,
50 + long length,
51 + long compressedLength,
52 + ZipCompressionMethod compressionMethod)
53 + : base(filePath, zipNumber, attributes, lastWriteTime, length)
54 + {
55 + this.compressedLength = compressedLength;
56 + this.compressionMethod = compressionMethod;
57 + }
58 +
59 + /// <summary>
60 + /// Initializes a new instance of the ZipFileInfo class with serialized data.
61 + /// </summary>
62 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
63 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
64 + protected ZipFileInfo(SerializationInfo info, StreamingContext context)
65 + : base(info, context)
66 + {
67 + this.compressedLength = info.GetInt64("compressedLength");
68 + }
69 +
70 + /// <summary>
71 + /// Gets the compressed size of the file in bytes.
72 + /// </summary>
73 + public long CompressedLength
74 + {
75 + get
76 + {
77 + return this.compressedLength;
78 + }
79 + }
80 +
81 + /// <summary>
82 + /// Gets the method used to compress this file.
83 + /// </summary>
84 + public ZipCompressionMethod CompressionMethod
85 + {
86 + get
87 + {
88 + return this.compressionMethod;
89 + }
90 + }
91 +
92 + /// <summary>
93 + /// Sets the SerializationInfo with information about the archive.
94 + /// </summary>
95 + /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
96 + /// <param name="context">The StreamingContext that contains contextual information
97 + /// about the source or destination.</param>
98 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
99 + {
100 + base.GetObjectData(info, context);
101 + info.AddValue("compressedLength", this.compressedLength);
102 + }
103 + }
104 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs new
+697
@@ -0,0 +1,697 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.Globalization;
7 + using System.IO;
8 + using System.Text;
9 + using System.Collections.Generic;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + [Flags]
13 + internal enum ZipFileFlags : ushort
14 + {
15 + None = 0x0000,
16 + Encrypt = 0x0001,
17 + CompressOption1 = 0x0002,
18 + CompressOption2 = 0x0004,
19 + DataDescriptor = 0x0008,
20 + StrongEncrypt = 0x0040,
21 + UTF8 = 0x0800
22 + }
23 +
24 + internal enum ZipExtraFileFieldType : ushort
25 + {
26 + ZIP64 = 0x0001,
27 + NTFS_TIMES = 0x000A,
28 + NTFS_ACLS = 0x4453,
29 + EXTIME = 0x5455
30 + }
31 +
32 + internal class ZipFileHeader
33 + {
34 + public const uint LFHSIG = 0x04034B50;
35 + public const uint CFHSIG = 0x02014B50;
36 +
37 + public const uint SPANSIG = 0x08074b50;
38 + public const uint SPANSIG2 = 0x30304b50;
39 +
40 + public const uint LFH_FIXEDSIZE = 30;
41 + public const uint CFH_FIXEDSIZE = 46;
42 +
43 + public ushort versionMadeBy;
44 + public ushort versionNeeded;
45 + public ZipFileFlags flags;
46 + public ZipCompressionMethod compressionMethod;
47 + public short lastModTime;
48 + public short lastModDate;
49 + public uint crc32;
50 + public uint compressedSize;
51 + public uint uncompressedSize;
52 + public ushort diskStart;
53 + public ushort internalFileAttrs;
54 + public uint externalFileAttrs;
55 + public uint localHeaderOffset;
56 + public string fileName;
57 + public ZipExtraFileField[] extraFields;
58 + public string fileComment;
59 + public bool zip64;
60 +
61 + public ZipFileHeader()
62 + {
63 + this.versionMadeBy = 20;
64 + this.versionNeeded = 20;
65 + }
66 +
67 + public ZipFileHeader(ZipFileInfo fileInfo, bool zip64)
68 + : this()
69 + {
70 + this.flags = ZipFileFlags.None;
71 + this.compressionMethod = fileInfo.CompressionMethod;
72 + this.fileName = Path.Combine(fileInfo.Path, fileInfo.Name);
73 + CompressionEngine.DateTimeToDosDateAndTime(
74 + fileInfo.LastWriteTime, out this.lastModDate, out this.lastModTime);
75 + this.zip64 = zip64;
76 +
77 + if (this.zip64)
78 + {
79 + this.compressedSize = UInt32.MaxValue;
80 + this.uncompressedSize = UInt32.MaxValue;
81 + this.diskStart = UInt16.MaxValue;
82 + this.versionMadeBy = 45;
83 + this.versionNeeded = 45;
84 + ZipExtraFileField field = new ZipExtraFileField();
85 + field.fieldType = ZipExtraFileFieldType.ZIP64;
86 + field.SetZip64Data(
87 + fileInfo.CompressedLength,
88 + fileInfo.Length,
89 + 0,
90 + fileInfo.ArchiveNumber);
91 + this.extraFields = new ZipExtraFileField[] { field };
92 + }
93 + else
94 + {
95 + this.compressedSize = (uint) fileInfo.CompressedLength;
96 + this.uncompressedSize = (uint) fileInfo.Length;
97 + this.diskStart = (ushort) fileInfo.ArchiveNumber;
98 + }
99 + }
100 +
101 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
102 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
103 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
104 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
105 + public void Update(
106 + long compressedSize,
107 + long uncompressedSize,
108 + uint crc32,
109 + long localHeaderOffset,
110 + int archiveNumber)
111 + {
112 + this.crc32 = crc32;
113 +
114 + if (this.zip64)
115 + {
116 + this.compressedSize = UInt32.MaxValue;
117 + this.uncompressedSize = UInt32.MaxValue;
118 + this.localHeaderOffset = UInt32.MaxValue;
119 + this.diskStart = UInt16.MaxValue;
120 +
121 + if (this.extraFields != null)
122 + {
123 + foreach (ZipExtraFileField field in this.extraFields)
124 + {
125 + if (field.fieldType == ZipExtraFileFieldType.ZIP64)
126 + {
127 + field.SetZip64Data(
128 + compressedSize,
129 + uncompressedSize,
130 + localHeaderOffset,
131 + archiveNumber);
132 + }
133 + }
134 + }
135 + }
136 + else
137 + {
138 + this.compressedSize = (uint) compressedSize;
139 + this.uncompressedSize = (uint) uncompressedSize;
140 + this.localHeaderOffset = (uint) localHeaderOffset;
141 + this.diskStart = (ushort) archiveNumber;
142 + }
143 + }
144 +
145 + public bool Read(Stream stream, bool central)
146 + {
147 + long startPos = stream.Position;
148 +
149 + if (stream.Length - startPos <
150 + (central ? CFH_FIXEDSIZE : LFH_FIXEDSIZE))
151 + {
152 + return false;
153 + }
154 +
155 + BinaryReader reader = new BinaryReader(stream);
156 + uint sig = reader.ReadUInt32();
157 +
158 + if (sig == SPANSIG || sig == SPANSIG2)
159 + {
160 + // Spanned zip files may optionally begin with a special marker.
161 + // Just ignore it and move on.
162 + sig = reader.ReadUInt32();
163 + }
164 +
165 + if (sig != (central ? CFHSIG : LFHSIG))
166 + {
167 + return false;
168 + }
169 +
170 + this.versionMadeBy = (central ? reader.ReadUInt16() : (ushort) 0);
171 + this.versionNeeded = reader.ReadUInt16();
172 + this.flags = (ZipFileFlags) reader.ReadUInt16();
173 + this.compressionMethod = (ZipCompressionMethod) reader.ReadUInt16();
174 + this.lastModTime = reader.ReadInt16();
175 + this.lastModDate = reader.ReadInt16();
176 + this.crc32 = reader.ReadUInt32();
177 + this.compressedSize = reader.ReadUInt32();
178 + this.uncompressedSize = reader.ReadUInt32();
179 +
180 + this.zip64 = this.uncompressedSize == UInt32.MaxValue;
181 +
182 + int fileNameLength = reader.ReadUInt16();
183 + int extraFieldLength = reader.ReadUInt16();
184 + int fileCommentLength;
185 +
186 + if (central)
187 + {
188 + fileCommentLength = reader.ReadUInt16();
189 +
190 + this.diskStart = reader.ReadUInt16();
191 + this.internalFileAttrs = reader.ReadUInt16();
192 + this.externalFileAttrs = reader.ReadUInt32();
193 + this.localHeaderOffset = reader.ReadUInt32();
194 + }
195 + else
196 + {
197 + fileCommentLength = 0;
198 + this.diskStart = 0;
199 + this.internalFileAttrs = 0;
200 + this.externalFileAttrs = 0;
201 + this.localHeaderOffset = 0;
202 + }
203 +
204 + if (stream.Length - stream.Position <
205 + fileNameLength + extraFieldLength + fileCommentLength)
206 + {
207 + return false;
208 + }
209 +
210 + Encoding headerEncoding = ((this.flags | ZipFileFlags.UTF8) != 0 ?
211 + Encoding.UTF8 : Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage));
212 +
213 + byte[] fileNameBytes = reader.ReadBytes(fileNameLength);
214 + this.fileName = headerEncoding.GetString(fileNameBytes).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
215 +
216 + List<ZipExtraFileField> fields = new List<ZipExtraFileField>();
217 + while (extraFieldLength > 0)
218 + {
219 + ZipExtraFileField field = new ZipExtraFileField();
220 + if (!field.Read(stream, ref extraFieldLength))
221 + {
222 + return false;
223 + }
224 + fields.Add(field);
225 + if (field.fieldType == ZipExtraFileFieldType.ZIP64)
226 + {
227 + this.zip64 = true;
228 + }
229 + }
230 + this.extraFields = fields.ToArray();
231 +
232 + byte[] fileCommentBytes = reader.ReadBytes(fileCommentLength);
233 + this.fileComment = headerEncoding.GetString(fileCommentBytes);
234 +
235 + return true;
236 + }
237 +
238 + public void Write(Stream stream, bool central)
239 + {
240 + byte[] fileNameBytes = (this.fileName != null
241 + ? Encoding.UTF8.GetBytes(this.fileName) : new byte[0]);
242 + byte[] fileCommentBytes = (this.fileComment != null
243 + ? Encoding.UTF8.GetBytes(this.fileComment) : new byte[0]);
244 + bool useUtf8 =
245 + (this.fileName != null && fileNameBytes.Length > this.fileName.Length) ||
246 + (this.fileComment != null && fileCommentBytes.Length > this.fileComment.Length);
247 + if (useUtf8)
248 + {
249 + this.flags |= ZipFileFlags.UTF8;
250 + }
251 +
252 + BinaryWriter writer = new BinaryWriter(stream);
253 + writer.Write(central ? CFHSIG : LFHSIG);
254 + if (central)
255 + {
256 + writer.Write(this.versionMadeBy);
257 + }
258 + writer.Write(this.versionNeeded);
259 + writer.Write((ushort) this.flags);
260 + writer.Write((ushort) this.compressionMethod);
261 + writer.Write(this.lastModTime);
262 + writer.Write(this.lastModDate);
263 + writer.Write(this.crc32);
264 + writer.Write(this.compressedSize);
265 + writer.Write(this.uncompressedSize);
266 +
267 + ushort extraFieldLength = 0;
268 + if (this.extraFields != null)
269 + {
270 + foreach (ZipExtraFileField field in this.extraFields)
271 + {
272 + if (field.data != null)
273 + {
274 + extraFieldLength += (ushort) (4 + field.data.Length);
275 + }
276 + }
277 + }
278 +
279 + writer.Write((ushort) fileNameBytes.Length);
280 + writer.Write(extraFieldLength);
281 +
282 + if (central)
283 + {
284 + writer.Write((ushort) fileCommentBytes.Length);
285 +
286 + writer.Write(this.diskStart);
287 + writer.Write(this.internalFileAttrs);
288 + writer.Write(this.externalFileAttrs);
289 + writer.Write(this.localHeaderOffset);
290 + }
291 +
292 + writer.Write(fileNameBytes);
293 +
294 + if (this.extraFields != null)
295 + {
296 + foreach (ZipExtraFileField field in this.extraFields)
297 + {
298 + if (field.data != null)
299 + {
300 + field.Write(stream);
301 + }
302 + }
303 + }
304 +
305 + if (central)
306 + {
307 + writer.Write(fileCommentBytes);
308 + }
309 + }
310 +
311 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
312 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
313 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
314 + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
315 + public void GetZip64Fields(
316 + out long compressedSize,
317 + out long uncompressedSize,
318 + out long localHeaderOffset,
319 + out int archiveNumber,
320 + out uint crc)
321 + {
322 + compressedSize = this.compressedSize;
323 + uncompressedSize = this.uncompressedSize;
324 + localHeaderOffset = this.localHeaderOffset;
325 + archiveNumber = this.diskStart;
326 + crc = this.crc32;
327 +
328 + foreach (ZipExtraFileField field in this.extraFields)
329 + {
330 + if (field.fieldType == ZipExtraFileFieldType.ZIP64)
331 + {
332 + field.GetZip64Data(
333 + out compressedSize,
334 + out uncompressedSize,
335 + out localHeaderOffset,
336 + out archiveNumber);
337 + }
338 + }
339 + }
340 +
341 + public ZipFileInfo ToZipFileInfo()
342 + {
343 + string name = this.fileName;
344 +
345 + long compressedSizeL;
346 + long uncompressedSizeL;
347 + long localHeaderOffsetL;
348 + int archiveNumberL;
349 + uint crc;
350 + this.GetZip64Fields(
351 + out compressedSizeL,
352 + out uncompressedSizeL,
353 + out localHeaderOffsetL,
354 + out archiveNumberL,
355 + out crc);
356 +
357 + DateTime dateTime;
358 + CompressionEngine.DosDateAndTimeToDateTime(
359 + this.lastModDate,
360 + this.lastModTime,
361 + out dateTime);
362 + FileAttributes attrs = FileAttributes.Normal;
363 + // TODO: look for attrs or times in extra fields
364 +
365 + return new ZipFileInfo(name, archiveNumberL, attrs, dateTime,
366 + uncompressedSizeL, compressedSizeL, this.compressionMethod);
367 + }
368 +
369 + public bool IsDirectory
370 + {
371 + get
372 + {
373 + return this.fileName != null &&
374 + (this.fileName.EndsWith("/", StringComparison.Ordinal) ||
375 + this.fileName.EndsWith("\\", StringComparison.Ordinal));
376 + }
377 + }
378 +
379 + public int GetSize(bool central)
380 + {
381 + int size = 30;
382 +
383 + int fileNameSize = (this.fileName != null
384 + ? Encoding.UTF8.GetByteCount(this.fileName) : 0);
385 + size += fileNameSize;
386 +
387 + if (this.extraFields != null)
388 + {
389 + foreach (ZipExtraFileField field in this.extraFields)
390 + {
391 + if (field.data != null)
392 + {
393 + size += 4 + field.data.Length;
394 + }
395 + }
396 + }
397 +
398 + if (central)
399 + {
400 + size += 16;
401 +
402 + int fileCommentSize = (this.fileComment != null
403 + ? Encoding.UTF8.GetByteCount(this.fileComment) : 0);
404 + size += fileCommentSize;
405 + }
406 +
407 + return size;
408 + }
409 + }
410 +
411 + internal class ZipExtraFileField
412 + {
413 + public ZipExtraFileFieldType fieldType;
414 + public byte[] data;
415 +
416 + public bool Read(Stream stream, ref int bytesRemaining)
417 + {
418 + if (bytesRemaining < 4)
419 + {
420 + return false;
421 + }
422 +
423 + BinaryReader reader = new BinaryReader(stream);
424 +
425 + this.fieldType = (ZipExtraFileFieldType) reader.ReadUInt16();
426 + ushort dataSize = reader.ReadUInt16();
427 + bytesRemaining -= 4;
428 +
429 + if (bytesRemaining < dataSize)
430 + {
431 + return false;
432 + }
433 +
434 + this.data = reader.ReadBytes(dataSize);
435 + bytesRemaining -= dataSize;
436 +
437 + return true;
438 + }
439 +
440 + public void Write(Stream stream)
441 + {
442 + BinaryWriter writer = new BinaryWriter(stream);
443 + writer.Write((ushort) this.fieldType);
444 +
445 + byte[] dataBytes = (this.data != null ? this.data : new byte[0]);
446 + writer.Write((ushort) dataBytes.Length);
447 + writer.Write(dataBytes);
448 + }
449 +
450 + public bool GetZip64Data(
451 + out long compressedSize,
452 + out long uncompressedSize,
453 + out long localHeaderOffset,
454 + out int diskStart)
455 + {
456 + uncompressedSize = 0;
457 + compressedSize = 0;
458 + localHeaderOffset = 0;
459 + diskStart = 0;
460 +
461 + if (this.fieldType != ZipExtraFileFieldType.ZIP64 ||
462 + this.data == null || this.data.Length != 28)
463 + {
464 + return false;
465 + }
466 +
467 + using (MemoryStream dataStream = new MemoryStream(this.data))
468 + {
469 + BinaryReader reader = new BinaryReader(dataStream);
470 + uncompressedSize = reader.ReadInt64();
471 + compressedSize = reader.ReadInt64();
472 + localHeaderOffset = reader.ReadInt64();
473 + diskStart = reader.ReadInt32();
474 + }
475 +
476 + return true;
477 + }
478 +
479 + public bool SetZip64Data(
480 + long compressedSize,
481 + long uncompressedSize,
482 + long localHeaderOffset,
483 + int diskStart)
484 + {
485 + if (this.fieldType != ZipExtraFileFieldType.ZIP64)
486 + {
487 + return false;
488 + }
489 +
490 + using (MemoryStream dataStream = new MemoryStream())
491 + {
492 + BinaryWriter writer = new BinaryWriter(dataStream);
493 + writer.Write(uncompressedSize);
494 + writer.Write(compressedSize);
495 + writer.Write(localHeaderOffset);
496 + writer.Write(diskStart);
497 + this.data = dataStream.ToArray();
498 + }
499 +
500 + return true;
501 + }
502 + }
503 +
504 + internal class ZipEndOfCentralDirectory
505 + {
506 + public const uint EOCDSIG = 0x06054B50;
507 + public const uint EOCD64SIG = 0x06064B50;
508 +
509 + public const uint EOCD_RECORD_FIXEDSIZE = 22;
510 + public const uint EOCD64_RECORD_FIXEDSIZE = 56;
511 +
512 + public ushort versionMadeBy;
513 + public ushort versionNeeded;
514 + public uint diskNumber;
515 + public uint dirStartDiskNumber;
516 + public long entriesOnDisk;
517 + public long totalEntries;
518 + public long dirSize;
519 + public long dirOffset;
520 + public string comment;
521 + public bool zip64;
522 +
523 + public ZipEndOfCentralDirectory()
524 + {
525 + this.versionMadeBy = 20;
526 + this.versionNeeded = 20;
527 + }
528 +
529 + public bool Read(Stream stream)
530 + {
531 + long startPos = stream.Position;
532 +
533 + if (stream.Length - startPos < EOCD_RECORD_FIXEDSIZE)
534 + {
535 + return false;
536 + }
537 +
538 + BinaryReader reader = new BinaryReader(stream);
539 + uint sig = reader.ReadUInt32();
540 +
541 + this.zip64 = false;
542 + if (sig != EOCDSIG)
543 + {
544 + if (sig == EOCD64SIG)
545 + {
546 + this.zip64 = true;
547 + }
548 + else
549 + {
550 + return false;
551 + }
552 + }
553 +
554 + if (this.zip64)
555 + {
556 + if (stream.Length - startPos < EOCD64_RECORD_FIXEDSIZE)
557 + {
558 + return false;
559 + }
560 +
561 + long recordSize = reader.ReadInt64();
562 + this.versionMadeBy = reader.ReadUInt16();
563 + this.versionNeeded = reader.ReadUInt16();
564 + this.diskNumber = reader.ReadUInt32();
565 + this.dirStartDiskNumber = reader.ReadUInt32();
566 + this.entriesOnDisk = reader.ReadInt64();
567 + this.totalEntries = reader.ReadInt64();
568 + this.dirSize = reader.ReadInt64();
569 + this.dirOffset = reader.ReadInt64();
570 +
571 + // Ignore any extended zip64 eocd data.
572 + long exDataSize = recordSize + 12 - EOCD64_RECORD_FIXEDSIZE;
573 +
574 + if (stream.Length - stream.Position < exDataSize)
575 + {
576 + return false;
577 + }
578 +
579 + stream.Seek(exDataSize, SeekOrigin.Current);
580 +
581 + this.comment = null;
582 + }
583 + else
584 + {
585 + this.diskNumber = reader.ReadUInt16();
586 + this.dirStartDiskNumber = reader.ReadUInt16();
587 + this.entriesOnDisk = reader.ReadUInt16();
588 + this.totalEntries = reader.ReadUInt16();
589 + this.dirSize = reader.ReadUInt32();
590 + this.dirOffset = reader.ReadUInt32();
591 +
592 + int commentLength = reader.ReadUInt16();
593 +
594 + if (stream.Length - stream.Position < commentLength)
595 + {
596 + return false;
597 + }
598 +
599 + byte[] commentBytes = reader.ReadBytes(commentLength);
600 + this.comment = Encoding.UTF8.GetString(commentBytes);
601 + }
602 +
603 + return true;
604 + }
605 +
606 + public void Write(Stream stream)
607 + {
608 + BinaryWriter writer = new BinaryWriter(stream);
609 +
610 + if (this.zip64)
611 + {
612 + writer.Write(EOCD64SIG);
613 + writer.Write((long) EOCD64_RECORD_FIXEDSIZE);
614 + writer.Write(this.versionMadeBy);
615 + writer.Write(this.versionNeeded);
616 + writer.Write(this.diskNumber);
617 + writer.Write(this.dirStartDiskNumber);
618 + writer.Write(this.entriesOnDisk);
619 + writer.Write(this.totalEntries);
620 + writer.Write(this.dirSize);
621 + writer.Write(this.dirOffset);
622 + }
623 + else
624 + {
625 + writer.Write(EOCDSIG);
626 + writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.diskNumber));
627 + writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.dirStartDiskNumber));
628 + writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.entriesOnDisk));
629 + writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.totalEntries));
630 + writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirSize));
631 + writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirOffset));
632 +
633 + byte[] commentBytes = (this.comment != null
634 + ? Encoding.UTF8.GetBytes(this.comment) : new byte[0]);
635 + writer.Write((ushort) commentBytes.Length);
636 + writer.Write(commentBytes);
637 + }
638 + }
639 +
640 + public int GetSize(bool zip64Size)
641 + {
642 + if (zip64Size)
643 + {
644 + return 56;
645 + }
646 + else
647 + {
648 + int commentSize = (this.comment != null
649 + ? Encoding.UTF8.GetByteCount(this.comment) : 0);
650 + return 22 + commentSize;
651 + }
652 + }
653 + }
654 +
655 + internal class Zip64EndOfCentralDirectoryLocator
656 + {
657 + public const uint EOCDL64SIG = 0x07064B50;
658 +
659 + public const uint EOCDL64_SIZE = 20;
660 +
661 + public uint dirStartDiskNumber;
662 + public long dirOffset;
663 + public uint totalDisks;
664 +
665 + public bool Read(Stream stream)
666 + {
667 + long startPos = stream.Position;
668 + if (stream.Length - startPos < EOCDL64_SIZE)
669 + {
670 + return false;
671 + }
672 +
673 + BinaryReader reader = new BinaryReader(stream);
674 + uint sig = reader.ReadUInt32();
675 +
676 + if (sig != EOCDL64SIG)
677 + {
678 + return false;
679 + }
680 +
681 + this.dirStartDiskNumber = reader.ReadUInt32();
682 + this.dirOffset = reader.ReadInt64();
683 + this.totalDisks = reader.ReadUInt32();
684 +
685 + return true;
686 + }
687 +
688 + public void Write(Stream stream)
689 + {
690 + BinaryWriter writer = new BinaryWriter(stream);
691 + writer.Write(EOCDL64SIG);
692 + writer.Write(this.dirStartDiskNumber);
693 + writer.Write(this.dirOffset);
694 + writer.Write(this.totalDisks);
695 + }
696 + }
697 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs new
+82
@@ -0,0 +1,82 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Runtime.Serialization;
8 +
9 + /// <summary>
10 + /// Object representing a zip file on disk; provides access to
11 + /// file-based operations on the zip file.
12 + /// </summary>
13 + /// <remarks>
14 + /// Generally, the methods on this class are much easier to use than the
15 + /// stream-based interfaces provided by the <see cref="ZipEngine"/> class.
16 + /// </remarks>
17 + [Serializable]
18 + public class ZipInfo : ArchiveInfo
19 + {
20 + /// <summary>
21 + /// Creates a new CabinetInfo object representing a zip file in a specified path.
22 + /// </summary>
23 + /// <param name="path">The path to the zip file. When creating a zip file, this file does not
24 + /// necessarily exist yet.</param>
25 + public ZipInfo(string path)
26 + : base(path)
27 + {
28 + }
29 +
30 + /// <summary>
31 + /// Initializes a new instance of the CabinetInfo class with serialized data.
32 + /// </summary>
33 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 + protected ZipInfo(SerializationInfo info, StreamingContext context)
36 + : base(info, context)
37 + {
38 + }
39 +
40 + /// <summary>
41 + /// Creates a compression engine that does the low-level work for
42 + /// this object.
43 + /// </summary>
44 + /// <returns>A new <see cref="ZipEngine"/> instance.</returns>
45 + /// <remarks>
46 + /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 + /// immediately after use.
48 + /// </remarks>
49 + protected override CompressionEngine CreateCompressionEngine()
50 + {
51 + return new ZipEngine();
52 + }
53 +
54 + /// <summary>
55 + /// Gets information about the files contained in the archive.
56 + /// </summary>
57 + /// <returns>A list of <see cref="ZipFileInfo"/> objects, each
58 + /// containing information about a file in the archive.</returns>
59 + public new IList<ZipFileInfo> GetFiles()
60 + {
61 + IList<ArchiveFileInfo> files = base.GetFiles();
62 + List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
63 + foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
64 + return zipFiles.AsReadOnly();
65 + }
66 +
67 + /// <summary>
68 + /// Gets information about the certain files contained in the archive file.
69 + /// </summary>
70 + /// <param name="searchPattern">The search string, such as
71 + /// &quot;*.txt&quot;.</param>
72 + /// <returns>A list of <see cref="ZipFileInfo"/> objects, each containing
73 + /// information about a file in the archive.</returns>
74 + public new IList<ZipFileInfo> GetFiles(string searchPattern)
75 + {
76 + IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 + List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
78 + foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
79 + return zipFiles.AsReadOnly();
80 + }
81 + }
82 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs new
+489
@@ -0,0 +1,489 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.IO.Compression;
8 + using System.Collections.Generic;
9 + using System.Globalization;
10 +
11 + public partial class ZipEngine
12 + {
13 + /// <summary>
14 + /// Creates a zip archive or chain of zip archives.
15 + /// </summary>
16 + /// <param name="streamContext">A context interface to handle opening
17 + /// and closing of archive and file streams.</param>
18 + /// <param name="files">An array of file lists. Each list is
19 + /// compressed into one stream in the archive.</param>
20 + /// <param name="maxArchiveSize">The maximum number of bytes for one archive
21 + /// before the contents are chained to the next archive, or zero for unlimited
22 + /// archive size.</param>
23 + /// <exception cref="ArchiveException">The archive could not be
24 + /// created.</exception>
25 + /// <remarks>
26 + /// The stream context implementation may provide a mapping from the file
27 + /// paths within the archive to the external file paths.
28 + /// </remarks>
29 + public override void Pack(
30 + IPackStreamContext streamContext,
31 + IEnumerable<string> files,
32 + long maxArchiveSize)
33 + {
34 + if (streamContext == null)
35 + {
36 + throw new ArgumentNullException("streamContext");
37 + }
38 +
39 + if (files == null)
40 + {
41 + throw new ArgumentNullException("files");
42 + }
43 +
44 + lock (this)
45 + {
46 + Stream archiveStream = null;
47 + try
48 + {
49 + this.ResetProgressData();
50 + this.totalArchives = 1;
51 +
52 + object forceZip64Value = streamContext.GetOption("forceZip64", null);
53 + bool forceZip64 = Convert.ToBoolean(
54 + forceZip64Value, CultureInfo.InvariantCulture);
55 +
56 + // Count the total number of files and bytes to be compressed.
57 + foreach (string file in files)
58 + {
59 + FileAttributes attributes;
60 + DateTime lastWriteTime;
61 + Stream fileStream = streamContext.OpenFileReadStream(
62 + file,
63 + out attributes,
64 + out lastWriteTime);
65 + if (fileStream != null)
66 + {
67 + this.totalFileBytes += fileStream.Length;
68 + this.totalFiles++;
69 + streamContext.CloseFileReadStream(file, fileStream);
70 + }
71 + }
72 +
73 + List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>();
74 + this.currentFileNumber = -1;
75 +
76 + if (this.currentArchiveName == null)
77 + {
78 + this.mainArchiveName = streamContext.GetArchiveName(0);
79 + this.currentArchiveName = this.mainArchiveName;
80 +
81 + if (String.IsNullOrEmpty(this.currentArchiveName))
82 + {
83 + throw new FileNotFoundException("No name provided for archive.");
84 + }
85 + }
86 +
87 + this.OnProgress(ArchiveProgressType.StartArchive);
88 +
89 + // Compress files one by one, saving header info for each.
90 + foreach (string file in files)
91 + {
92 + ZipFileHeader fileHeader = this.PackOneFile(
93 + streamContext,
94 + file,
95 + maxArchiveSize,
96 + forceZip64,
97 + ref archiveStream);
98 +
99 + if (fileHeader != null)
100 + {
101 + fileHeaders.Add(fileHeader);
102 + }
103 +
104 + this.currentArchiveTotalBytes = (archiveStream != null ?
105 + archiveStream.Position : 0);
106 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
107 + }
108 +
109 + bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue;
110 +
111 + // Write the central directory composed of all the file headers.
112 + uint centralDirStartArchiveNumber = 0;
113 + long centralDirStartPosition = 0;
114 + long centralDirSize = 0;
115 + for (int i = 0; i < fileHeaders.Count; i++)
116 + {
117 + ZipFileHeader fileHeader = fileHeaders[i];
118 +
119 + int headerSize = fileHeader.GetSize(true);
120 + centralDirSize += headerSize;
121 +
122 + this.CheckArchiveWriteStream(
123 + streamContext,
124 + maxArchiveSize,
125 + headerSize,
126 + ref archiveStream);
127 +
128 + if (i == 0)
129 + {
130 + centralDirStartArchiveNumber = (uint) this.currentArchiveNumber;
131 + centralDirStartPosition = archiveStream.Position;
132 + }
133 +
134 + fileHeader.Write(archiveStream, true);
135 + if (fileHeader.zip64)
136 + {
137 + zip64 = true;
138 + }
139 + }
140 +
141 + this.currentArchiveTotalBytes =
142 + (archiveStream != null ? archiveStream.Position : 0);
143 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
144 +
145 + ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
146 + eocd.dirStartDiskNumber = centralDirStartArchiveNumber;
147 + eocd.entriesOnDisk = fileHeaders.Count;
148 + eocd.totalEntries = fileHeaders.Count;
149 + eocd.dirSize = centralDirSize;
150 + eocd.dirOffset = centralDirStartPosition;
151 + eocd.comment = this.comment;
152 +
153 + Zip64EndOfCentralDirectoryLocator eocdl =
154 + new Zip64EndOfCentralDirectoryLocator();
155 +
156 + int maxFooterSize = eocd.GetSize(false);
157 + if (archiveStream != null && (zip64 || archiveStream.Position >
158 + ((long) UInt32.MaxValue) - eocd.GetSize(false)))
159 + {
160 + maxFooterSize += eocd.GetSize(true) + (int)
161 + Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE;
162 + zip64 = true;
163 + }
164 +
165 + this.CheckArchiveWriteStream(
166 + streamContext,
167 + maxArchiveSize,
168 + maxFooterSize,
169 + ref archiveStream);
170 + eocd.diskNumber = (uint) this.currentArchiveNumber;
171 +
172 + if (zip64)
173 + {
174 + eocd.versionMadeBy = 45;
175 + eocd.versionNeeded = 45;
176 + eocd.zip64 = true;
177 + eocdl.dirOffset = archiveStream.Position;
178 + eocdl.dirStartDiskNumber = (uint) this.currentArchiveNumber;
179 + eocdl.totalDisks = (uint) this.currentArchiveNumber + 1;
180 + eocd.Write(archiveStream);
181 + eocdl.Write(archiveStream);
182 +
183 + eocd.dirOffset = UInt32.MaxValue;
184 + eocd.dirStartDiskNumber = UInt16.MaxValue;
185 + }
186 +
187 + eocd.zip64 = false;
188 + eocd.Write(archiveStream);
189 +
190 + this.currentArchiveTotalBytes = archiveStream.Position;
191 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
192 + }
193 + finally
194 + {
195 + if (archiveStream != null)
196 + {
197 + streamContext.CloseArchiveWriteStream(
198 + this.currentArchiveNumber, this.mainArchiveName, archiveStream);
199 + this.OnProgress(ArchiveProgressType.FinishArchive);
200 + }
201 + }
202 + }
203 + }
204 +
205 + /// <summary>
206 + /// Moves to the next archive in the sequence if necessary.
207 + /// </summary>
208 + private void CheckArchiveWriteStream(
209 + IPackStreamContext streamContext,
210 + long maxArchiveSize,
211 + long requiredSize,
212 + ref Stream archiveStream)
213 + {
214 + if (archiveStream != null &&
215 + archiveStream.Length > 0 && maxArchiveSize > 0)
216 + {
217 + long sizeRemaining = maxArchiveSize - archiveStream.Length;
218 + if (sizeRemaining < requiredSize)
219 + {
220 + string nextArchiveName = streamContext.GetArchiveName(
221 + this.currentArchiveNumber + 1);
222 +
223 + if (String.IsNullOrEmpty(nextArchiveName))
224 + {
225 + throw new FileNotFoundException("No name provided for archive #" +
226 + this.currentArchiveNumber + 1);
227 + }
228 +
229 + this.currentArchiveTotalBytes = archiveStream.Position;
230 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
231 +
232 + streamContext.CloseArchiveWriteStream(
233 + this.currentArchiveNumber,
234 + nextArchiveName,
235 + archiveStream);
236 + archiveStream = null;
237 +
238 + this.OnProgress(ArchiveProgressType.FinishArchive);
239 +
240 + this.currentArchiveNumber++;
241 + this.totalArchives++;
242 + this.currentArchiveBytesProcessed = 0;
243 + this.currentArchiveTotalBytes = 0;
244 + }
245 + }
246 +
247 + if (archiveStream == null)
248 + {
249 + if (this.currentArchiveNumber > 0)
250 + {
251 + this.OnProgress(ArchiveProgressType.StartArchive);
252 + }
253 +
254 + archiveStream = streamContext.OpenArchiveWriteStream(
255 + this.currentArchiveNumber, this.mainArchiveName, true, this);
256 +
257 + if (archiveStream == null)
258 + {
259 + throw new FileNotFoundException("Stream not provided for archive #" +
260 + this.currentArchiveNumber);
261 + }
262 + }
263 + }
264 +
265 + /// <summary>
266 + /// Adds one file to a zip archive in the process of being created.
267 + /// </summary>
268 + private ZipFileHeader PackOneFile(
269 + IPackStreamContext streamContext,
270 + string file,
271 + long maxArchiveSize,
272 + bool forceZip64,
273 + ref Stream archiveStream)
274 + {
275 + Stream fileStream = null;
276 + int headerArchiveNumber = 0;
277 + try
278 + {
279 + // TODO: call GetOption to get compression method for the specific file
280 + ZipCompressionMethod compressionMethod = ZipCompressionMethod.Deflate;
281 + if (this.CompressionLevel == WixToolset.Dtf.Compression.CompressionLevel.None)
282 + {
283 + compressionMethod = ZipCompressionMethod.Store;
284 + }
285 +
286 + Converter<Stream, Stream> compressionStreamCreator;
287 + if (!ZipEngine.compressionStreamCreators.TryGetValue(
288 + compressionMethod, out compressionStreamCreator))
289 + {
290 + return null;
291 + }
292 +
293 + FileAttributes attributes;
294 + DateTime lastWriteTime;
295 + fileStream = streamContext.OpenFileReadStream(
296 + file, out attributes, out lastWriteTime);
297 + if (fileStream == null)
298 + {
299 + return null;
300 + }
301 +
302 + this.currentFileName = file;
303 + this.currentFileNumber++;
304 +
305 + this.currentFileTotalBytes = fileStream.Length;
306 + this.currentFileBytesProcessed = 0;
307 + this.OnProgress(ArchiveProgressType.StartFile);
308 +
309 + ZipFileInfo fileInfo = new ZipFileInfo(
310 + file,
311 + this.currentArchiveNumber,
312 + attributes,
313 + lastWriteTime,
314 + fileStream.Length,
315 + 0,
316 + compressionMethod);
317 +
318 + bool zip64 = forceZip64 || fileStream.Length >= (long) UInt32.MaxValue;
319 + ZipFileHeader fileHeader = new ZipFileHeader(fileInfo, zip64);
320 +
321 + this.CheckArchiveWriteStream(
322 + streamContext,
323 + maxArchiveSize,
324 + fileHeader.GetSize(false),
325 + ref archiveStream);
326 +
327 + long headerPosition = archiveStream.Position;
328 + fileHeader.Write(archiveStream, false);
329 + headerArchiveNumber = this.currentArchiveNumber;
330 +
331 + uint crc;
332 + long bytesWritten = this.PackFileBytes(
333 + streamContext,
334 + fileStream,
335 + maxArchiveSize,
336 + compressionStreamCreator,
337 + ref archiveStream,
338 + out crc);
339 +
340 + fileHeader.Update(
341 + bytesWritten,
342 + fileStream.Length,
343 + crc,
344 + headerPosition,
345 + headerArchiveNumber);
346 +
347 + streamContext.CloseFileReadStream(file, fileStream);
348 + fileStream = null;
349 +
350 + // Go back and rewrite the updated file header.
351 + if (this.currentArchiveNumber == headerArchiveNumber)
352 + {
353 + long fileEndPosition = archiveStream.Position;
354 + archiveStream.Seek(headerPosition, SeekOrigin.Begin);
355 + fileHeader.Write(archiveStream, false);
356 + archiveStream.Seek(fileEndPosition, SeekOrigin.Begin);
357 + }
358 + else
359 + {
360 + // The file spanned archives, so temporarily reopen
361 + // the archive where it started.
362 + string headerArchiveName = streamContext.GetArchiveName(
363 + headerArchiveNumber + 1);
364 + Stream headerStream = null;
365 + try
366 + {
367 + headerStream = streamContext.OpenArchiveWriteStream(
368 + headerArchiveNumber, headerArchiveName, false, this);
369 + headerStream.Seek(headerPosition, SeekOrigin.Begin);
370 + fileHeader.Write(headerStream, false);
371 + }
372 + finally
373 + {
374 + if (headerStream != null)
375 + {
376 + streamContext.CloseArchiveWriteStream(
377 + headerArchiveNumber, headerArchiveName, headerStream);
378 + }
379 + }
380 + }
381 +
382 + this.OnProgress(ArchiveProgressType.FinishFile);
383 +
384 + return fileHeader;
385 + }
386 + finally
387 + {
388 + if (fileStream != null)
389 + {
390 + streamContext.CloseFileReadStream(
391 + this.currentFileName, fileStream);
392 + }
393 + }
394 + }
395 +
396 + /// <summary>
397 + /// Writes compressed bytes of one file to the archive,
398 + /// keeping track of the CRC and number of bytes written.
399 + /// </summary>
400 + private long PackFileBytes(
401 + IPackStreamContext streamContext,
402 + Stream fileStream,
403 + long maxArchiveSize,
404 + Converter<Stream, Stream> compressionStreamCreator,
405 + ref Stream archiveStream,
406 + out uint crc)
407 + {
408 + long writeStartPosition = archiveStream.Position;
409 + long bytesWritten = 0;
410 + CrcStream fileCrcStream = new CrcStream(fileStream);
411 +
412 + ConcatStream concatStream = new ConcatStream(
413 + delegate(ConcatStream s)
414 + {
415 + Stream sourceStream = s.Source;
416 + bytesWritten += sourceStream.Position - writeStartPosition;
417 +
418 + this.CheckArchiveWriteStream(
419 + streamContext,
420 + maxArchiveSize,
421 + 1,
422 + ref sourceStream);
423 +
424 + writeStartPosition = sourceStream.Position;
425 + s.Source = sourceStream;
426 + });
427 +
428 + concatStream.Source = archiveStream;
429 +
430 + if (maxArchiveSize > 0)
431 + {
432 + concatStream.SetLength(maxArchiveSize);
433 + }
434 +
435 + Stream compressionStream = compressionStreamCreator(concatStream);
436 +
437 + try
438 + {
439 + byte[] buf = new byte[4096];
440 + long bytesRemaining = fileStream.Length;
441 + int counter = 0;
442 + while (bytesRemaining > 0)
443 + {
444 + int count = (int) Math.Min(
445 + bytesRemaining, (long) buf.Length);
446 +
447 + count = fileCrcStream.Read(buf, 0, count);
448 + if (count <= 0)
449 + {
450 + throw new ZipException(
451 + "Failed to read file: " + this.currentFileName);
452 + }
453 +
454 + compressionStream.Write(buf, 0, count);
455 + bytesRemaining -= count;
456 +
457 + this.fileBytesProcessed += count;
458 + this.currentFileBytesProcessed += count;
459 + this.currentArchiveTotalBytes = concatStream.Source.Position;
460 + this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
461 +
462 + if (++counter % 16 == 0) // Report every 64K
463 + {
464 + this.OnProgress(ArchiveProgressType.PartialFile);
465 + }
466 + }
467 +
468 + if (compressionStream is DeflateStream)
469 + {
470 + compressionStream.Close();
471 + }
472 + else
473 + {
474 + compressionStream.Flush();
475 + }
476 + }
477 + finally
478 + {
479 + archiveStream = concatStream.Source;
480 + }
481 +
482 + bytesWritten += archiveStream.Position - writeStartPosition;
483 +
484 + crc = fileCrcStream.Crc;
485 +
486 + return bytesWritten;
487 + }
488 + }
489 +}
src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs new
+336
@@ -0,0 +1,336 @@
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 +namespace WixToolset.Dtf.Compression.Zip
4 +{
5 + using System;
6 + using System.IO;
7 + using System.IO.Compression;
8 + using System.Collections.Generic;
9 +
10 + public partial class ZipEngine
11 + {
12 + /// <summary>
13 + /// Extracts files from a zip archive or archive chain.
14 + /// </summary>
15 + /// <param name="streamContext">A context interface to handle opening
16 + /// and closing of archive and file streams.</param>
17 + /// <param name="fileFilter">An optional predicate that can determine
18 + /// which files to process.</param>
19 + /// <exception cref="ArchiveException">The archive provided
20 + /// by the stream context is not valid.</exception>
21 + /// <remarks>
22 + /// The <paramref name="fileFilter"/> predicate takes an internal file
23 + /// path and returns true to include the file or false to exclude it.
24 + /// </remarks>
25 + public override void Unpack(
26 + IUnpackStreamContext streamContext,
27 + Predicate<string> fileFilter)
28 + {
29 + if (streamContext == null)
30 + {
31 + throw new ArgumentNullException("streamContext");
32 + }
33 +
34 + lock (this)
35 + {
36 + IList<ZipFileHeader> allHeaders = this.GetCentralDirectory(streamContext);
37 + if (allHeaders == null)
38 + {
39 + throw new ZipException("Zip central directory not found.");
40 + }
41 +
42 + IList<ZipFileHeader> headers = new List<ZipFileHeader>(allHeaders.Count);
43 + foreach (ZipFileHeader header in allHeaders)
44 + {
45 + if (!header.IsDirectory &&
46 + (fileFilter == null || fileFilter(header.fileName)))
47 + {
48 + headers.Add(header);
49 + }
50 + }
51 +
52 + this.ResetProgressData();
53 +
54 + // Count the total number of files and bytes to be compressed.
55 + this.totalFiles = headers.Count;
56 + foreach (ZipFileHeader header in headers)
57 + {
58 + long compressedSize;
59 + long uncompressedSize;
60 + long localHeaderOffset;
61 + int archiveNumber;
62 + uint crc;
63 + header.GetZip64Fields(
64 + out compressedSize,
65 + out uncompressedSize,
66 + out localHeaderOffset,
67 + out archiveNumber,
68 + out crc);
69 +
70 + this.totalFileBytes += uncompressedSize;
71 + if (archiveNumber >= this.totalArchives)
72 + {
73 + this.totalArchives = (short) (archiveNumber + 1);
74 + }
75 + }
76 +
77 + this.currentArchiveNumber = -1;
78 + this.currentFileNumber = -1;
79 + Stream archiveStream = null;
80 + try
81 + {
82 + foreach (ZipFileHeader header in headers)
83 + {
84 + this.currentFileNumber++;
85 + this.UnpackOneFile(streamContext, header, ref archiveStream);
86 + }
87 + }
88 + finally
89 + {
90 + if (archiveStream != null)
91 + {
92 + streamContext.CloseArchiveReadStream(
93 + 0, String.Empty, archiveStream);
94 + this.currentArchiveNumber--;
95 + this.OnProgress(ArchiveProgressType.FinishArchive);
96 + }
97 + }
98 + }
99 + }
100 +
101 + /// <summary>
102 + /// Unpacks a single file from an archive or archive chain.
103 + /// </summary>
104 + private void UnpackOneFile(
105 + IUnpackStreamContext streamContext,
106 + ZipFileHeader header,
107 + ref Stream archiveStream)
108 + {
109 + ZipFileInfo fileInfo = null;
110 + Stream fileStream = null;
111 + try
112 + {
113 + Converter<Stream, Stream> compressionStreamCreator;
114 + if (!ZipEngine.decompressionStreamCreators.TryGetValue(
115 + header.compressionMethod, out compressionStreamCreator))
116 + {
117 + // Silently skip files of an unsupported compression method.
118 + return;
119 + }
120 +
121 + long compressedSize;
122 + long uncompressedSize;
123 + long localHeaderOffset;
124 + int archiveNumber;
125 + uint crc;
126 + header.GetZip64Fields(
127 + out compressedSize,
128 + out uncompressedSize,
129 + out localHeaderOffset,
130 + out archiveNumber,
131 + out crc);
132 +
133 + if (this.currentArchiveNumber != archiveNumber + 1)
134 + {
135 + if (archiveStream != null)
136 + {
137 + streamContext.CloseArchiveReadStream(
138 + this.currentArchiveNumber,
139 + String.Empty,
140 + archiveStream);
141 + archiveStream = null;
142 +
143 + this.OnProgress(ArchiveProgressType.FinishArchive);
144 + this.currentArchiveName = null;
145 + }
146 +
147 + this.currentArchiveNumber = (short) (archiveNumber + 1);
148 + this.currentArchiveBytesProcessed = 0;
149 + this.currentArchiveTotalBytes = 0;
150 +
151 + archiveStream = this.OpenArchive(
152 + streamContext, this.currentArchiveNumber);
153 +
154 + FileStream archiveFileStream = archiveStream as FileStream;
155 + this.currentArchiveName = (archiveFileStream != null ?
156 + Path.GetFileName(archiveFileStream.Name) : null);
157 +
158 + this.currentArchiveTotalBytes = archiveStream.Length;
159 + this.currentArchiveNumber--;
160 + this.OnProgress(ArchiveProgressType.StartArchive);
161 + this.currentArchiveNumber++;
162 + }
163 +
164 + archiveStream.Seek(localHeaderOffset, SeekOrigin.Begin);
165 +
166 + ZipFileHeader localHeader = new ZipFileHeader();
167 + if (!localHeader.Read(archiveStream, false) ||
168 + !ZipEngine.AreFilePathsEqual(localHeader.fileName, header.fileName))
169 + {
170 + string msg = "Could not read file: " + header.fileName;
171 + throw new ZipException(msg);
172 + }
173 +
174 + fileInfo = header.ToZipFileInfo();
175 +
176 + fileStream = streamContext.OpenFileWriteStream(
177 + fileInfo.FullName,
178 + fileInfo.Length,
179 + fileInfo.LastWriteTime);
180 +
181 + if (fileStream != null)
182 + {
183 + this.currentFileName = header.fileName;
184 + this.currentFileBytesProcessed = 0;
185 + this.currentFileTotalBytes = fileInfo.Length;
186 + this.currentArchiveNumber--;
187 + this.OnProgress(ArchiveProgressType.StartFile);
188 + this.currentArchiveNumber++;
189 +
190 + this.UnpackFileBytes(
191 + streamContext,
192 + fileInfo.FullName,
193 + fileInfo.CompressedLength,
194 + fileInfo.Length,
195 + header.crc32,
196 + fileStream,
197 + compressionStreamCreator,
198 + ref archiveStream);
199 + }
200 + }
201 + finally
202 + {
203 + if (fileStream != null)
204 + {
205 + streamContext.CloseFileWriteStream(
206 + fileInfo.FullName,
207 + fileStream,
208 + fileInfo.Attributes,
209 + fileInfo.LastWriteTime);
210 +
211 + this.currentArchiveNumber--;
212 + this.OnProgress(ArchiveProgressType.FinishFile);
213 + this.currentArchiveNumber++;
214 + }
215 + }
216 + }
217 +
218 + /// <summary>
219 + /// Compares two internal file paths while ignoring case and slash differences.
220 + /// </summary>
221 + /// <param name="path1">The first path to compare.</param>
222 + /// <param name="path2">The second path to compare.</param>
223 + /// <returns>True if the paths are equivalent.</returns>
224 + private static bool AreFilePathsEqual(string path1, string path2)
225 + {
226 + path1 = path1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
227 + path2 = path2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
228 + return String.Compare(path1, path2, StringComparison.OrdinalIgnoreCase) == 0;
229 + }
230 +
231 + private Stream OpenArchive(IUnpackStreamContext streamContext, int archiveNumber)
232 + {
233 + Stream archiveStream = streamContext.OpenArchiveReadStream(
234 + archiveNumber, String.Empty, this);
235 + if (archiveStream == null && archiveNumber != 0)
236 + {
237 + archiveStream = streamContext.OpenArchiveReadStream(
238 + 0, String.Empty, this);
239 + }
240 +
241 + if (archiveStream == null)
242 + {
243 + throw new FileNotFoundException("Archive stream not provided.");
244 + }
245 +
246 + return archiveStream;
247 + }
248 +
249 + /// <summary>
250 + /// Decompresses bytes for one file from an archive or archive chain,
251 + /// checking the crc at the end.
252 + /// </summary>
253 + private void UnpackFileBytes(
254 + IUnpackStreamContext streamContext,
255 + string fileName,
256 + long compressedSize,
257 + long uncompressedSize,
258 + uint crc,
259 + Stream fileStream,
260 + Converter<Stream, Stream> compressionStreamCreator,
261 + ref Stream archiveStream)
262 + {
263 + CrcStream crcStream = new CrcStream(fileStream);
264 +
265 + ConcatStream concatStream = new ConcatStream(
266 + delegate(ConcatStream s)
267 + {
268 + this.currentArchiveBytesProcessed = s.Source.Position;
269 + streamContext.CloseArchiveReadStream(
270 + this.currentArchiveNumber,
271 + String.Empty,
272 + s.Source);
273 +
274 + this.currentArchiveNumber--;
275 + this.OnProgress(ArchiveProgressType.FinishArchive);
276 + this.currentArchiveNumber += 2;
277 + this.currentArchiveName = null;
278 + this.currentArchiveBytesProcessed = 0;
279 + this.currentArchiveTotalBytes = 0;
280 +
281 + s.Source = this.OpenArchive(streamContext, this.currentArchiveNumber);
282 +
283 + FileStream archiveFileStream = s.Source as FileStream;
284 + this.currentArchiveName = (archiveFileStream != null ?
285 + Path.GetFileName(archiveFileStream.Name) : null);
286 +
287 + this.currentArchiveTotalBytes = s.Source.Length;
288 + this.currentArchiveNumber--;
289 + this.OnProgress(ArchiveProgressType.StartArchive);
290 + this.currentArchiveNumber++;
291 + });
292 +
293 + concatStream.Source = archiveStream;
294 + concatStream.SetLength(compressedSize);
295 +
296 + Stream decompressionStream = compressionStreamCreator(concatStream);
297 +
298 + try
299 + {
300 + byte[] buf = new byte[4096];
301 + long bytesRemaining = uncompressedSize;
302 + int counter = 0;
303 + while (bytesRemaining > 0)
304 + {
305 + int count = (int) Math.Min(buf.Length, bytesRemaining);
306 + count = decompressionStream.Read(buf, 0, count);
307 + crcStream.Write(buf, 0, count);
308 + bytesRemaining -= count;
309 +
310 + this.fileBytesProcessed += count;
311 + this.currentFileBytesProcessed += count;
312 + this.currentArchiveBytesProcessed = concatStream.Source.Position;
313 +
314 + if (++counter % 16 == 0) // Report every 64K
315 + {
316 + this.currentArchiveNumber--;
317 + this.OnProgress(ArchiveProgressType.PartialFile);
318 + this.currentArchiveNumber++;
319 + }
320 + }
321 + }
322 + finally
323 + {
324 + archiveStream = concatStream.Source;
325 + }
326 +
327 + crcStream.Flush();
328 +
329 + if (crcStream.Crc != crc)
330 + {
331 + throw new ZipException("CRC check failed for file: " + fileName);
332 + }
333 + }
334 + }
335 +}
336 +
src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs new
+57
@@ -0,0 +1,57 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Runtime.Serialization;
8 +
9 + /// <summary>
10 + /// Base exception class for compression operations. Compression libraries should
11 + /// derive subclass exceptions with more specific error information relevent to the
12 + /// file format.
13 + /// </summary>
14 + [Serializable]
15 + public class ArchiveException : IOException
16 + {
17 + /// <summary>
18 + /// Creates a new ArchiveException with a specified error message and a reference to the
19 + /// inner exception that is the cause of this exception.
20 + /// </summary>
21 + /// <param name="message">The message that describes the error.</param>
22 + /// <param name="innerException">The exception that is the cause of the current exception. If the
23 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
24 + /// is raised in a catch block that handles the inner exception.</param>
25 + public ArchiveException(string message, Exception innerException)
26 + : base(message, innerException)
27 + {
28 + }
29 +
30 + /// <summary>
31 + /// Creates a new ArchiveException with a specified error message.
32 + /// </summary>
33 + /// <param name="message">The message that describes the error.</param>
34 + public ArchiveException(string message)
35 + : this(message, null)
36 + {
37 + }
38 +
39 + /// <summary>
40 + /// Creates a new ArchiveException.
41 + /// </summary>
42 + public ArchiveException()
43 + : this(null, null)
44 + {
45 + }
46 +
47 + /// <summary>
48 + /// Initializes a new instance of the ArchiveException class with serialized data.
49 + /// </summary>
50 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
51 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
52 + protected ArchiveException(SerializationInfo info, StreamingContext context)
53 + : base(info, context)
54 + {
55 + }
56 + }
57 +}
src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs new
+430
@@ -0,0 +1,430 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Runtime.Serialization;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + /// <summary>
11 + /// Abstract object representing a compressed file within an archive;
12 + /// provides operations for getting the file properties and unpacking
13 + /// the file.
14 + /// </summary>
15 + [Serializable]
16 + public abstract class ArchiveFileInfo : FileSystemInfo
17 + {
18 + private ArchiveInfo archiveInfo;
19 + private string name;
20 + private string path;
21 +
22 + private bool initialized;
23 + private bool exists;
24 + private int archiveNumber;
25 + private FileAttributes attributes;
26 + private DateTime lastWriteTime;
27 + private long length;
28 +
29 + /// <summary>
30 + /// Creates a new ArchiveFileInfo object representing a file within
31 + /// an archive in a specified path.
32 + /// </summary>
33 + /// <param name="archiveInfo">An object representing the archive
34 + /// containing the file.</param>
35 + /// <param name="filePath">The path to the file within the archive.
36 + /// Usually, this is a simple file name, but if the archive contains
37 + /// a directory structure this may include the directory.</param>
38 + protected ArchiveFileInfo(ArchiveInfo archiveInfo, string filePath)
39 + : base()
40 + {
41 + if (filePath == null)
42 + {
43 + throw new ArgumentNullException("filePath");
44 + }
45 +
46 + this.Archive = archiveInfo;
47 +
48 + this.name = System.IO.Path.GetFileName(filePath);
49 + this.path = System.IO.Path.GetDirectoryName(filePath);
50 +
51 + this.attributes = FileAttributes.Normal;
52 + this.lastWriteTime = DateTime.MinValue;
53 + }
54 +
55 + /// <summary>
56 + /// Creates a new ArchiveFileInfo object with all parameters specified;
57 + /// used by subclasses when reading the metadata out of an archive.
58 + /// </summary>
59 + /// <param name="filePath">The internal path and name of the file in
60 + /// the archive.</param>
61 + /// <param name="archiveNumber">The archive number where the file
62 + /// starts.</param>
63 + /// <param name="attributes">The stored attributes of the file.</param>
64 + /// <param name="lastWriteTime">The stored last write time of the
65 + /// file.</param>
66 + /// <param name="length">The uncompressed size of the file.</param>
67 + protected ArchiveFileInfo(
68 + string filePath,
69 + int archiveNumber,
70 + FileAttributes attributes,
71 + DateTime lastWriteTime,
72 + long length)
73 + : this(null, filePath)
74 + {
75 + this.exists = true;
76 + this.archiveNumber = archiveNumber;
77 + this.attributes = attributes;
78 + this.lastWriteTime = lastWriteTime;
79 + this.length = length;
80 + this.initialized = true;
81 + }
82 +
83 + /// <summary>
84 + /// Initializes a new instance of the ArchiveFileInfo class with
85 + /// serialized data.
86 + /// </summary>
87 + /// <param name="info">The SerializationInfo that holds the serialized
88 + /// object data about the exception being thrown.</param>
89 + /// <param name="context">The StreamingContext that contains contextual
90 + /// information about the source or destination.</param>
91 + protected ArchiveFileInfo(SerializationInfo info, StreamingContext context)
92 + : base(info, context)
93 + {
94 + this.archiveInfo = (ArchiveInfo) info.GetValue(
95 + "archiveInfo", typeof(ArchiveInfo));
96 + this.name = info.GetString("name");
97 + this.path = info.GetString("path");
98 + this.initialized = info.GetBoolean("initialized");
99 + this.exists = info.GetBoolean("exists");
100 + this.archiveNumber = info.GetInt32("archiveNumber");
101 + this.attributes = (FileAttributes) info.GetValue(
102 + "attributes", typeof(FileAttributes));
103 + this.lastWriteTime = info.GetDateTime("lastWriteTime");
104 + this.length = info.GetInt64("length");
105 + }
106 +
107 + /// <summary>
108 + /// Gets the name of the file.
109 + /// </summary>
110 + /// <value>The name of the file, not including any path.</value>
111 + public override string Name
112 + {
113 + get
114 + {
115 + return this.name;
116 + }
117 + }
118 +
119 + /// <summary>
120 + /// Gets the internal path of the file in the archive.
121 + /// </summary>
122 + /// <value>The internal path of the file in the archive, not including
123 + /// the file name.</value>
124 + public string Path
125 + {
126 + get
127 + {
128 + return this.path;
129 + }
130 + }
131 +
132 + /// <summary>
133 + /// Gets the full path to the file.
134 + /// </summary>
135 + /// <value>The full path to the file, including the full path to the
136 + /// archive, the internal path in the archive, and the file name.</value>
137 + /// <remarks>
138 + /// For example, the path <c>"C:\archive.cab\file.txt"</c> refers to
139 + /// a file "file.txt" inside the archive "archive.cab".
140 + /// </remarks>
141 + public override string FullName
142 + {
143 + get
144 + {
145 + string fullName = System.IO.Path.Combine(this.Path, this.Name);
146 +
147 + if (this.Archive != null)
148 + {
149 + fullName = System.IO.Path.Combine(this.ArchiveName, fullName);
150 + }
151 +
152 + return fullName;
153 + }
154 + }
155 +
156 + /// <summary>
157 + /// Gets or sets the archive that contains this file.
158 + /// </summary>
159 + /// <value>
160 + /// The ArchiveInfo instance that retrieved this file information -- this
161 + /// may be null if the ArchiveFileInfo object was returned directly from
162 + /// a stream.
163 + /// </value>
164 + public ArchiveInfo Archive
165 + {
166 + get
167 + {
168 + return (ArchiveInfo) this.archiveInfo;
169 + }
170 +
171 + internal set
172 + {
173 + this.archiveInfo = value;
174 +
175 + // protected instance members inherited from FileSystemInfo:
176 + this.OriginalPath = (value != null ? value.FullName : null);
177 + this.FullPath = this.OriginalPath;
178 + }
179 + }
180 +
181 + /// <summary>
182 + /// Gets the full path of the archive that contains this file.
183 + /// </summary>
184 + /// <value>The full path of the archive that contains this file.</value>
185 + public string ArchiveName
186 + {
187 + get
188 + {
189 + return this.Archive != null ? this.Archive.FullName : null;
190 + }
191 + }
192 +
193 + /// <summary>
194 + /// Gets the number of the archive where this file starts.
195 + /// </summary>
196 + /// <value>The number of the archive where this file starts.</value>
197 + /// <remarks>A single archive or the first archive in a chain is
198 + /// numbered 0.</remarks>
199 + public int ArchiveNumber
200 + {
201 + get
202 + {
203 + return this.archiveNumber;
204 + }
205 + }
206 +
207 + /// <summary>
208 + /// Checks if the file exists within the archive.
209 + /// </summary>
210 + /// <value>True if the file exists, false otherwise.</value>
211 + public override bool Exists
212 + {
213 + get
214 + {
215 + if (!this.initialized)
216 + {
217 + this.Refresh();
218 + }
219 +
220 + return this.exists;
221 + }
222 + }
223 +
224 + /// <summary>
225 + /// Gets the uncompressed size of the file.
226 + /// </summary>
227 + /// <value>The uncompressed size of the file in bytes.</value>
228 + public long Length
229 + {
230 + get
231 + {
232 + if (!this.initialized)
233 + {
234 + this.Refresh();
235 + }
236 +
237 + return this.length;
238 + }
239 + }
240 +
241 + /// <summary>
242 + /// Gets the attributes of the file.
243 + /// </summary>
244 + /// <value>The attributes of the file as stored in the archive.</value>
245 + public new FileAttributes Attributes
246 + {
247 + get
248 + {
249 + if (!this.initialized)
250 + {
251 + this.Refresh();
252 + }
253 +
254 + return this.attributes;
255 + }
256 + }
257 +
258 + /// <summary>
259 + /// Gets the last modification time of the file.
260 + /// </summary>
261 + /// <value>The last modification time of the file as stored in the
262 + /// archive.</value>
263 + public new DateTime LastWriteTime
264 + {
265 + get
266 + {
267 + if (!this.initialized)
268 + {
269 + this.Refresh();
270 + }
271 +
272 + return this.lastWriteTime;
273 + }
274 + }
275 +
276 + /// <summary>
277 + /// Sets the SerializationInfo with information about the archive.
278 + /// </summary>
279 + /// <param name="info">The SerializationInfo that holds the serialized
280 + /// object data.</param>
281 + /// <param name="context">The StreamingContext that contains contextual
282 + /// information about the source or destination.</param>
283 + public override void GetObjectData(
284 + SerializationInfo info, StreamingContext context)
285 + {
286 + base.GetObjectData(info, context);
287 + info.AddValue("archiveInfo", this.archiveInfo);
288 + info.AddValue("name", this.name);
289 + info.AddValue("path", this.path);
290 + info.AddValue("initialized", this.initialized);
291 + info.AddValue("exists", this.exists);
292 + info.AddValue("archiveNumber", this.archiveNumber);
293 + info.AddValue("attributes", this.attributes);
294 + info.AddValue("lastWriteTime", this.lastWriteTime);
295 + info.AddValue("length", this.length);
296 + }
297 +
298 + /// <summary>
299 + /// Gets the full path to the file.
300 + /// </summary>
301 + /// <returns>The same as <see cref="FullName"/></returns>
302 + public override string ToString()
303 + {
304 + return this.FullName;
305 + }
306 +
307 + /// <summary>
308 + /// Deletes the file. NOT SUPPORTED.
309 + /// </summary>
310 + /// <exception cref="NotSupportedException">Files cannot be deleted
311 + /// from an existing archive.</exception>
312 + public override void Delete()
313 + {
314 + throw new NotSupportedException();
315 + }
316 +
317 + /// <summary>
318 + /// Refreshes the attributes and other cached information about the file,
319 + /// by re-reading the information from the archive.
320 + /// </summary>
321 + public new void Refresh()
322 + {
323 + base.Refresh();
324 +
325 + if (this.Archive != null)
326 + {
327 + string filePath = System.IO.Path.Combine(this.Path, this.Name);
328 + ArchiveFileInfo updatedFile = this.Archive.GetFile(filePath);
329 + if (updatedFile == null)
330 + {
331 + throw new FileNotFoundException(
332 + "File not found in archive.", filePath);
333 + }
334 +
335 + this.Refresh(updatedFile);
336 + }
337 + }
338 +
339 + /// <summary>
340 + /// Extracts the file.
341 + /// </summary>
342 + /// <param name="destFileName">The destination path where the file
343 + /// will be extracted.</param>
344 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
345 + public void CopyTo(string destFileName)
346 + {
347 + this.CopyTo(destFileName, false);
348 + }
349 +
350 + /// <summary>
351 + /// Extracts the file, optionally overwriting any existing file.
352 + /// </summary>
353 + /// <param name="destFileName">The destination path where the file
354 + /// will be extracted.</param>
355 + /// <param name="overwrite">If true, <paramref name="destFileName"/>
356 + /// will be overwritten if it exists.</param>
357 + /// <exception cref="IOException"><paramref name="overwrite"/> is false
358 + /// and <paramref name="destFileName"/> exists.</exception>
359 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
360 + public void CopyTo(string destFileName, bool overwrite)
361 + {
362 + if (destFileName == null)
363 + {
364 + throw new ArgumentNullException("destFileName");
365 + }
366 +
367 + if (!overwrite && File.Exists(destFileName))
368 + {
369 + throw new IOException();
370 + }
371 +
372 + if (this.Archive == null)
373 + {
374 + throw new InvalidOperationException();
375 + }
376 +
377 + this.Archive.UnpackFile(
378 + System.IO.Path.Combine(this.Path, this.Name), destFileName);
379 + }
380 +
381 + /// <summary>
382 + /// Opens the archive file for reading without actually extracting the
383 + /// file to disk.
384 + /// </summary>
385 + /// <returns>
386 + /// A stream for reading directly from the packed file. Like any stream
387 + /// this should be closed/disposed as soon as it is no longer needed.
388 + /// </returns>
389 + public Stream OpenRead()
390 + {
391 + return this.Archive.OpenRead(System.IO.Path.Combine(this.Path, this.Name));
392 + }
393 +
394 + /// <summary>
395 + /// Opens the archive file reading text with UTF-8 encoding without
396 + /// actually extracting the file to disk.
397 + /// </summary>
398 + /// <returns>
399 + /// A reader for reading text directly from the packed file. Like any reader
400 + /// this should be closed/disposed as soon as it is no longer needed.
401 + /// </returns>
402 + /// <remarks>
403 + /// To open an archived text file with different encoding, use the
404 + /// <see cref="OpenRead" /> method and pass the returned stream to one of
405 + /// the <see cref="StreamReader" /> constructor overloads.
406 + /// </remarks>
407 + public StreamReader OpenText()
408 + {
409 + return this.Archive.OpenText(System.IO.Path.Combine(this.Path, this.Name));
410 + }
411 +
412 + /// <summary>
413 + /// Refreshes the information in this object with new data retrieved
414 + /// from an archive.
415 + /// </summary>
416 + /// <param name="newFileInfo">Fresh instance for the same file just
417 + /// read from the archive.</param>
418 + /// <remarks>
419 + /// Subclasses may override this method to refresh sublcass fields.
420 + /// However they should always call the base implementation first.
421 + /// </remarks>
422 + protected virtual void Refresh(ArchiveFileInfo newFileInfo)
423 + {
424 + this.exists = newFileInfo.exists;
425 + this.length = newFileInfo.length;
426 + this.attributes = newFileInfo.attributes;
427 + this.lastWriteTime = newFileInfo.lastWriteTime;
428 + }
429 + }
430 +}
src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs new
+664
@@ -0,0 +1,664 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 +
9 + /// <summary>
10 + /// Provides a basic implementation of the archive pack and unpack stream context
11 + /// interfaces, based on a list of archive files, a default directory, and an
12 + /// optional mapping from internal to external file paths.
13 + /// </summary>
14 + /// <remarks>
15 + /// This class can also handle creating or extracting chained archive packages.
16 + /// </remarks>
17 + public class ArchiveFileStreamContext
18 + : IPackStreamContext, IUnpackStreamContext
19 + {
20 + private IList<string> archiveFiles;
21 + private string directory;
22 + private IDictionary<string, string> files;
23 + private bool extractOnlyNewerFiles;
24 + private bool enableOffsetOpen;
25 +
26 + #region Constructors
27 +
28 + /// <summary>
29 + /// Creates a new ArchiveFileStreamContext with a archive file and
30 + /// no default directory or file mapping.
31 + /// </summary>
32 + /// <param name="archiveFile">The path to a archive file that will be
33 + /// created or extracted.</param>
34 + public ArchiveFileStreamContext(string archiveFile)
35 + : this(archiveFile, null, null)
36 + {
37 + }
38 +
39 + /// <summary>
40 + /// Creates a new ArchiveFileStreamContext with a archive file, default
41 + /// directory and mapping from internal to external file paths.
42 + /// </summary>
43 + /// <param name="archiveFile">The path to a archive file that will be
44 + /// created or extracted.</param>
45 + /// <param name="directory">The default root directory where files will be
46 + /// located, optional.</param>
47 + /// <param name="files">A mapping from internal file paths to external file
48 + /// paths, optional.</param>
49 + /// <remarks>
50 + /// If the mapping is not null and a file is not included in the mapping,
51 + /// the file will be skipped.
52 + /// <para>If the external path in the mapping is a simple file name or
53 + /// relative file path, it will be concatenated onto the default directory,
54 + /// if one was specified.</para>
55 + /// <para>For more about how the default directory and files mapping are
56 + /// used, see <see cref="OpenFileReadStream"/> and
57 + /// <see cref="OpenFileWriteStream"/>.</para>
58 + /// </remarks>
59 + public ArchiveFileStreamContext(
60 + string archiveFile,
61 + string directory,
62 + IDictionary<string, string> files)
63 + : this(new string[] { archiveFile }, directory, files)
64 + {
65 + if (archiveFile == null)
66 + {
67 + throw new ArgumentNullException("archiveFile");
68 + }
69 + }
70 +
71 + /// <summary>
72 + /// Creates a new ArchiveFileStreamContext with a list of archive files,
73 + /// a default directory and a mapping from internal to external file paths.
74 + /// </summary>
75 + /// <param name="archiveFiles">A list of paths to archive files that will be
76 + /// created or extracted.</param>
77 + /// <param name="directory">The default root directory where files will be
78 + /// located, optional.</param>
79 + /// <param name="files">A mapping from internal file paths to external file
80 + /// paths, optional.</param>
81 + /// <remarks>
82 + /// When creating chained archives, the <paramref name="archiveFiles"/> list
83 + /// should include at least enough archives to handle the entire set of
84 + /// input files, based on the maximum archive size that is passed to the
85 + /// <see cref="CompressionEngine"/>.<see
86 + /// cref="CompressionEngine.Pack(IPackStreamContext,IEnumerable&lt;string&gt;,long)"/>.
87 + /// <para>If the mapping is not null and a file is not included in the mapping,
88 + /// the file will be skipped.</para>
89 + /// <para>If the external path in the mapping is a simple file name or
90 + /// relative file path, it will be concatenated onto the default directory,
91 + /// if one was specified.</para>
92 + /// <para>For more about how the default directory and files mapping are used,
93 + /// see <see cref="OpenFileReadStream"/> and
94 + /// <see cref="OpenFileWriteStream"/>.</para>
95 + /// </remarks>
96 + public ArchiveFileStreamContext(
97 + IList<string> archiveFiles,
98 + string directory,
99 + IDictionary<string, string> files)
100 + {
101 + if (archiveFiles == null || archiveFiles.Count == 0)
102 + {
103 + throw new ArgumentNullException("archiveFiles");
104 + }
105 +
106 + this.archiveFiles = archiveFiles;
107 + this.directory = directory;
108 + this.files = files;
109 + }
110 +
111 + #endregion
112 +
113 + #region Properties
114 +
115 + /// <summary>
116 + /// Gets or sets the list of archive files that are created or extracted.
117 + /// </summary>
118 + /// <value>The list of archive files that are created or extracted.</value>
119 + public IList<string> ArchiveFiles
120 + {
121 + get
122 + {
123 + return this.archiveFiles;
124 + }
125 + }
126 +
127 + /// <summary>
128 + /// Gets or sets the default root directory where files are located.
129 + /// </summary>
130 + /// <value>The default root directory where files are located.</value>
131 + /// <remarks>
132 + /// For details about how the default directory is used,
133 + /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
134 + /// </remarks>
135 + public string Directory
136 + {
137 + get
138 + {
139 + return this.directory;
140 + }
141 + }
142 +
143 + /// <summary>
144 + /// Gets or sets the mapping from internal file paths to external file paths.
145 + /// </summary>
146 + /// <value>A mapping from internal file paths to external file paths.</value>
147 + /// <remarks>
148 + /// For details about how the files mapping is used,
149 + /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
150 + /// </remarks>
151 + public IDictionary<string, string> Files
152 + {
153 + get
154 + {
155 + return this.files;
156 + }
157 + }
158 +
159 + /// <summary>
160 + /// Gets or sets a flag that can prevent extracted files from overwriting
161 + /// newer files that already exist.
162 + /// </summary>
163 + /// <value>True to prevent overwriting newer files that already exist
164 + /// during extraction; false to always extract from the archive regardless
165 + /// of existing files.</value>
166 + public bool ExtractOnlyNewerFiles
167 + {
168 + get
169 + {
170 + return this.extractOnlyNewerFiles;
171 + }
172 +
173 + set
174 + {
175 + this.extractOnlyNewerFiles = value;
176 + }
177 + }
178 +
179 + /// <summary>
180 + /// Gets or sets a flag that enables creating or extracting an archive
181 + /// at an offset within an existing file. (This is typically used to open
182 + /// archive-based self-extracting packages.)
183 + /// </summary>
184 + /// <value>True to search an existing package file for an archive offset
185 + /// or the end of the file;/ false to always create or open a plain
186 + /// archive file.</value>
187 + public bool EnableOffsetOpen
188 + {
189 + get
190 + {
191 + return this.enableOffsetOpen;
192 + }
193 +
194 + set
195 + {
196 + this.enableOffsetOpen = value;
197 + }
198 + }
199 +
200 + #endregion
201 +
202 + #region IPackStreamContext Members
203 +
204 + /// <summary>
205 + /// Gets the name of the archive with a specified number.
206 + /// </summary>
207 + /// <param name="archiveNumber">The 0-based index of the archive within
208 + /// the chain.</param>
209 + /// <returns>The name of the requested archive. May be an empty string
210 + /// for non-chained archives, but may never be null.</returns>
211 + /// <remarks>This method returns the file name of the archive from the
212 + /// <see cref="archiveFiles"/> list with the specified index, or an empty
213 + /// string if the archive number is outside the bounds of the list. The
214 + /// file name should not include any directory path.</remarks>
215 + public virtual string GetArchiveName(int archiveNumber)
216 + {
217 + if (archiveNumber < this.archiveFiles.Count)
218 + {
219 + return Path.GetFileName(this.archiveFiles[archiveNumber]);
220 + }
221 +
222 + return String.Empty;
223 + }
224 +
225 + /// <summary>
226 + /// Opens a stream for writing an archive.
227 + /// </summary>
228 + /// <param name="archiveNumber">The 0-based index of the archive within
229 + /// the chain.</param>
230 + /// <param name="archiveName">The name of the archive that was returned
231 + /// by <see cref="GetArchiveName"/>.</param>
232 + /// <param name="truncate">True if the stream should be truncated when
233 + /// opened (if it already exists); false if an existing stream is being
234 + /// re-opened for writing additional data.</param>
235 + /// <param name="compressionEngine">Instance of the compression engine
236 + /// doing the operations.</param>
237 + /// <returns>A writable Stream where the compressed archive bytes will be
238 + /// written, or null to cancel the archive creation.</returns>
239 + /// <remarks>
240 + /// This method opens the file from the <see cref="ArchiveFiles"/> list
241 + /// with the specified index. If the archive number is outside the bounds
242 + /// of the list, this method returns null.
243 + /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method
244 + /// will seek to the start of any existing archive in the file, or to the
245 + /// end of the file if the existing file is not an archive.</para>
246 + /// </remarks>
247 + public virtual Stream OpenArchiveWriteStream(
248 + int archiveNumber,
249 + string archiveName,
250 + bool truncate,
251 + CompressionEngine compressionEngine)
252 + {
253 + if (archiveNumber >= this.archiveFiles.Count)
254 + {
255 + return null;
256 + }
257 +
258 + if (String.IsNullOrEmpty(archiveName))
259 + {
260 + throw new ArgumentNullException("archiveName");
261 + }
262 +
263 + // All archives must be in the same directory,
264 + // so always use the directory from the first archive.
265 + string archiveFile = Path.Combine(
266 + Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
267 + Stream stream = File.Open(
268 + archiveFile,
269 + (truncate ? FileMode.OpenOrCreate : FileMode.Open),
270 + FileAccess.ReadWrite);
271 +
272 + if (this.enableOffsetOpen)
273 + {
274 + long offset = compressionEngine.FindArchiveOffset(
275 + new DuplicateStream(stream));
276 +
277 + // If this is not an archive file, append the archive to it.
278 + if (offset < 0)
279 + {
280 + offset = stream.Length;
281 + }
282 +
283 + if (offset > 0)
284 + {
285 + stream = new OffsetStream(stream, offset);
286 + }
287 +
288 + stream.Seek(0, SeekOrigin.Begin);
289 + }
290 +
291 + if (truncate)
292 + {
293 + // Truncate the stream, in case a larger old archive starts here.
294 + stream.SetLength(0);
295 + }
296 +
297 + return stream;
298 + }
299 +
300 + /// <summary>
301 + /// Closes a stream where an archive package was written.
302 + /// </summary>
303 + /// <param name="archiveNumber">The 0-based index of the archive within
304 + /// the chain.</param>
305 + /// <param name="archiveName">The name of the archive that was previously
306 + /// returned by <see cref="GetArchiveName"/>.</param>
307 + /// <param name="stream">A stream that was previously returned by
308 + /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
309 + public virtual void CloseArchiveWriteStream(
310 + int archiveNumber,
311 + string archiveName,
312 + Stream stream)
313 + {
314 + if (stream != null)
315 + {
316 + stream.Close();
317 +
318 + FileStream fileStream = stream as FileStream;
319 + if (fileStream != null)
320 + {
321 + string streamFile = fileStream.Name;
322 + if (!String.IsNullOrEmpty(archiveName) &&
323 + archiveName != Path.GetFileName(streamFile))
324 + {
325 + string archiveFile = Path.Combine(
326 + Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
327 + if (File.Exists(archiveFile))
328 + {
329 + File.Delete(archiveFile);
330 + }
331 + File.Move(streamFile, archiveFile);
332 + }
333 + }
334 + }
335 + }
336 +
337 + /// <summary>
338 + /// Opens a stream to read a file that is to be included in an archive.
339 + /// </summary>
340 + /// <param name="path">The path of the file within the archive.</param>
341 + /// <param name="attributes">The returned attributes of the opened file,
342 + /// to be stored in the archive.</param>
343 + /// <param name="lastWriteTime">The returned last-modified time of the
344 + /// opened file, to be stored in the archive.</param>
345 + /// <returns>A readable Stream where the file bytes will be read from
346 + /// before they are compressed, or null to skip inclusion of the file and
347 + /// continue to the next file.</returns>
348 + /// <remarks>
349 + /// This method opens a file using the following logic:
350 + /// <list>
351 + /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
352 + /// are both null, the path is treated as relative to the current directory,
353 + /// and that file is opened.</item>
354 + /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
355 + /// mapping is null, the path is treated as relative to that directory, and
356 + /// that file is opened.</item>
357 + /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
358 + /// mapping is not null, the path parameter is used as a key into the mapping,
359 + /// and the resulting value is the file path that is opened, relative to the
360 + /// current directory (or it may be an absolute path). If no mapping exists,
361 + /// the file is skipped.</item>
362 + /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
363 + /// mapping are specified, the path parameter is used as a key into the
364 + /// mapping, and the resulting value is the file path that is opened, relative
365 + /// to the specified directory (or it may be an absolute path). If no mapping
366 + /// exists, the file is skipped.</item>
367 + /// </list>
368 + /// </remarks>
369 + public virtual Stream OpenFileReadStream(
370 + string path, out FileAttributes attributes, out DateTime lastWriteTime)
371 + {
372 + string filePath = this.TranslateFilePath(path);
373 +
374 + if (filePath == null)
375 + {
376 + attributes = FileAttributes.Normal;
377 + lastWriteTime = DateTime.Now;
378 + return null;
379 + }
380 +
381 + attributes = File.GetAttributes(filePath);
382 + lastWriteTime = File.GetLastWriteTime(filePath);
383 + return File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
384 + }
385 +
386 + /// <summary>
387 + /// Closes a stream that has been used to read a file.
388 + /// </summary>
389 + /// <param name="path">The path of the file within the archive; the same as
390 + /// the path provided when the stream was opened.</param>
391 + /// <param name="stream">A stream that was previously returned by
392 + /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
393 + public virtual void CloseFileReadStream(string path, Stream stream)
394 + {
395 + if (stream != null)
396 + {
397 + stream.Close();
398 + }
399 + }
400 +
401 + /// <summary>
402 + /// Gets extended parameter information specific to the compression format
403 + /// being used.
404 + /// </summary>
405 + /// <param name="optionName">Name of the option being requested.</param>
406 + /// <param name="parameters">Parameters for the option; for per-file options,
407 + /// the first parameter is typically the internal file path.</param>
408 + /// <returns>Option value, or null to use the default behavior.</returns>
409 + /// <remarks>
410 + /// This implementation does not handle any options. Subclasses may override
411 + /// this method to allow for non-default behavior.
412 + /// </remarks>
413 + public virtual object GetOption(string optionName, object[] parameters)
414 + {
415 + return null;
416 + }
417 +
418 + #endregion
419 +
420 + #region IUnpackStreamContext Members
421 +
422 + /// <summary>
423 + /// Opens the archive stream for reading.
424 + /// </summary>
425 + /// <param name="archiveNumber">The zero-based index of the archive to
426 + /// open.</param>
427 + /// <param name="archiveName">The name of the archive being opened.</param>
428 + /// <param name="compressionEngine">Instance of the compression engine
429 + /// doing the operations.</param>
430 + /// <returns>A stream from which archive bytes are read, or null to cancel
431 + /// extraction of the archive.</returns>
432 + /// <remarks>
433 + /// This method opens the file from the <see cref="ArchiveFiles"/> list with
434 + /// the specified index. If the archive number is outside the bounds of the
435 + /// list, this method returns null.
436 + /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method will
437 + /// seek to the start of any existing archive in the file, or to the end of
438 + /// the file if the existing file is not an archive.</para>
439 + /// </remarks>
440 + public virtual Stream OpenArchiveReadStream(
441 + int archiveNumber, string archiveName, CompressionEngine compressionEngine)
442 + {
443 + if (archiveNumber >= this.archiveFiles.Count)
444 + {
445 + return null;
446 + }
447 +
448 + string archiveFile = this.archiveFiles[archiveNumber];
449 + Stream stream = File.Open(
450 + archiveFile, FileMode.Open, FileAccess.Read, FileShare.Read);
451 +
452 + if (this.enableOffsetOpen)
453 + {
454 + long offset = compressionEngine.FindArchiveOffset(
455 + new DuplicateStream(stream));
456 + if (offset > 0)
457 + {
458 + stream = new OffsetStream(stream, offset);
459 + }
460 + else
461 + {
462 + stream.Seek(0, SeekOrigin.Begin);
463 + }
464 + }
465 +
466 + return stream;
467 + }
468 +
469 + /// <summary>
470 + /// Closes a stream where an archive was read.
471 + /// </summary>
472 + /// <param name="archiveNumber">The archive number of the stream
473 + /// to close.</param>
474 + /// <param name="archiveName">The name of the archive being closed.</param>
475 + /// <param name="stream">The stream that was previously returned by
476 + /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
477 + public virtual void CloseArchiveReadStream(
478 + int archiveNumber, string archiveName, Stream stream)
479 + {
480 + if (stream != null)
481 + {
482 + stream.Close();
483 + }
484 + }
485 +
486 + /// <summary>
487 + /// Opens a stream for writing extracted file bytes.
488 + /// </summary>
489 + /// <param name="path">The path of the file within the archive.</param>
490 + /// <param name="fileSize">The uncompressed size of the file to be
491 + /// extracted.</param>
492 + /// <param name="lastWriteTime">The last write time of the file to be
493 + /// extracted.</param>
494 + /// <returns>A stream where extracted file bytes are to be written, or null
495 + /// to skip extraction of the file and continue to the next file.</returns>
496 + /// <remarks>
497 + /// This method opens a file using the following logic:
498 + /// <list>
499 + /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
500 + /// are both null, the path is treated as relative to the current directory,
501 + /// and that file is opened.</item>
502 + /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
503 + /// mapping is null, the path is treated as relative to that directory, and
504 + /// that file is opened.</item>
505 + /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
506 + /// mapping is not null, the path parameter is used as a key into the mapping,
507 + /// and the resulting value is the file path that is opened, relative to the
508 + /// current directory (or it may be an absolute path). If no mapping exists,
509 + /// the file is skipped.</item>
510 + /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
511 + /// mapping are specified, the path parameter is used as a key into the
512 + /// mapping, and the resulting value is the file path that is opened,
513 + /// relative to the specified directory (or it may be an absolute path).
514 + /// If no mapping exists, the file is skipped.</item>
515 + /// </list>
516 + /// <para>If the <see cref="ExtractOnlyNewerFiles"/> flag is set, the file
517 + /// is skipped if a file currently exists in the same path with an equal
518 + /// or newer write time.</para>
519 + /// </remarks>
520 + public virtual Stream OpenFileWriteStream(
521 + string path,
522 + long fileSize,
523 + DateTime lastWriteTime)
524 + {
525 + string filePath = this.TranslateFilePath(path);
526 +
527 + if (filePath == null)
528 + {
529 + return null;
530 + }
531 +
532 + FileInfo file = new FileInfo(filePath);
533 + if (file.Exists)
534 + {
535 + if (this.extractOnlyNewerFiles && lastWriteTime != DateTime.MinValue)
536 + {
537 + if (file.LastWriteTime >= lastWriteTime)
538 + {
539 + return null;
540 + }
541 + }
542 +
543 + // Clear attributes that will prevent overwriting the file.
544 + // (The final attributes will be set after the file is unpacked.)
545 + FileAttributes attributesToClear =
546 + FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System;
547 + if ((file.Attributes & attributesToClear) != 0)
548 + {
549 + file.Attributes &= ~attributesToClear;
550 + }
551 + }
552 +
553 + if (!file.Directory.Exists)
554 + {
555 + file.Directory.Create();
556 + }
557 +
558 + return File.Open(
559 + filePath, FileMode.Create, FileAccess.Write, FileShare.None);
560 + }
561 +
562 + /// <summary>
563 + /// Closes a stream where an extracted file was written.
564 + /// </summary>
565 + /// <param name="path">The path of the file within the archive.</param>
566 + /// <param name="stream">The stream that was previously returned by
567 + /// <see cref="OpenFileWriteStream"/> and is now ready to be closed.</param>
568 + /// <param name="attributes">The attributes of the extracted file.</param>
569 + /// <param name="lastWriteTime">The last write time of the file.</param>
570 + /// <remarks>
571 + /// After closing the extracted file stream, this method applies the date
572 + /// and attributes to that file.
573 + /// </remarks>
574 + public virtual void CloseFileWriteStream(
575 + string path,
576 + Stream stream,
577 + FileAttributes attributes,
578 + DateTime lastWriteTime)
579 + {
580 + if (stream != null)
581 + {
582 + stream.Close();
583 + }
584 +
585 + string filePath = this.TranslateFilePath(path);
586 + if (filePath != null)
587 + {
588 + FileInfo file = new FileInfo(filePath);
589 +
590 + if (lastWriteTime != DateTime.MinValue)
591 + {
592 + try
593 + {
594 + file.LastWriteTime = lastWriteTime;
595 + }
596 + catch (ArgumentException)
597 + {
598 + }
599 + catch (IOException)
600 + {
601 + }
602 + }
603 +
604 + try
605 + {
606 + file.Attributes = attributes;
607 + }
608 + catch (IOException)
609 + {
610 + }
611 + }
612 + }
613 +
614 + #endregion
615 +
616 + #region Private utility methods
617 +
618 + /// <summary>
619 + /// Translates an internal file path to an external file path using the
620 + /// <see cref="Directory"/> and the <see cref="Files"/> mapping, according to
621 + /// rules documented in <see cref="OpenFileReadStream"/> and
622 + /// <see cref="OpenFileWriteStream"/>.
623 + /// </summary>
624 + /// <param name="path">The path of the file with the archive.</param>
625 + /// <returns>The external path of the file, or null if there is no
626 + /// valid translation.</returns>
627 + private string TranslateFilePath(string path)
628 + {
629 + string filePath;
630 + if (this.files != null)
631 + {
632 + filePath = this.files[path];
633 + }
634 + else
635 + {
636 + this.ValidateArchivePath(path);
637 +
638 + filePath = path;
639 + }
640 +
641 + if (filePath != null)
642 + {
643 + if (this.directory != null)
644 + {
645 + filePath = Path.Combine(this.directory, filePath);
646 + }
647 + }
648 +
649 + return filePath;
650 + }
651 +
652 + private void ValidateArchivePath(string filePath)
653 + {
654 + string basePath = Path.GetFullPath(String.IsNullOrEmpty(this.directory) ? Environment.CurrentDirectory : this.directory);
655 + string path = Path.GetFullPath(Path.Combine(basePath, filePath));
656 + if (!path.StartsWith(basePath, StringComparison.InvariantCultureIgnoreCase))
657 + {
658 + throw new InvalidDataException("Archive cannot contain files with absolute or traversal paths.");
659 + }
660 + }
661 +
662 + #endregion
663 + }
664 +}
src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs new
+781
@@ -0,0 +1,781 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Text;
10 + using System.Text.RegularExpressions;
11 + using System.Runtime.Serialization;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// Abstract object representing a compressed archive on disk;
16 + /// provides access to file-based operations on the archive.
17 + /// </summary>
18 + [Serializable]
19 + public abstract class ArchiveInfo : FileSystemInfo
20 + {
21 + /// <summary>
22 + /// Creates a new ArchiveInfo object representing an archive in a
23 + /// specified path.
24 + /// </summary>
25 + /// <param name="path">The path to the archive. When creating an archive,
26 + /// this file does not necessarily exist yet.</param>
27 + protected ArchiveInfo(string path) : base()
28 + {
29 + if (path == null)
30 + {
31 + throw new ArgumentNullException("path");
32 + }
33 +
34 + // protected instance members inherited from FileSystemInfo:
35 + this.OriginalPath = path;
36 + this.FullPath = Path.GetFullPath(path);
37 + }
38 +
39 + /// <summary>
40 + /// Initializes a new instance of the ArchiveInfo class with serialized data.
41 + /// </summary>
42 + /// <param name="info">The SerializationInfo that holds the serialized object
43 + /// data about the exception being thrown.</param>
44 + /// <param name="context">The StreamingContext that contains contextual
45 + /// information about the source or destination.</param>
46 + protected ArchiveInfo(SerializationInfo info, StreamingContext context)
47 + : base(info, context)
48 + {
49 + }
50 +
51 + /// <summary>
52 + /// Gets the directory that contains the archive.
53 + /// </summary>
54 + /// <value>A DirectoryInfo object representing the parent directory of the
55 + /// archive.</value>
56 + public DirectoryInfo Directory
57 + {
58 + get
59 + {
60 + return new DirectoryInfo(Path.GetDirectoryName(this.FullName));
61 + }
62 + }
63 +
64 + /// <summary>
65 + /// Gets the full path of the directory that contains the archive.
66 + /// </summary>
67 + /// <value>The full path of the directory that contains the archive.</value>
68 + public string DirectoryName
69 + {
70 + get
71 + {
72 + return Path.GetDirectoryName(this.FullName);
73 + }
74 + }
75 +
76 + /// <summary>
77 + /// Gets the size of the archive.
78 + /// </summary>
79 + /// <value>The size of the archive in bytes.</value>
80 + public long Length
81 + {
82 + get
83 + {
84 + return new FileInfo(this.FullName).Length;
85 + }
86 + }
87 +
88 + /// <summary>
89 + /// Gets the file name of the archive.
90 + /// </summary>
91 + /// <value>The file name of the archive, not including any path.</value>
92 + public override string Name
93 + {
94 + get
95 + {
96 + return Path.GetFileName(this.FullName);
97 + }
98 + }
99 +
100 + /// <summary>
101 + /// Checks if the archive exists.
102 + /// </summary>
103 + /// <value>True if the archive exists; else false.</value>
104 + public override bool Exists
105 + {
106 + get
107 + {
108 + return File.Exists(this.FullName);
109 + }
110 + }
111 +
112 + /// <summary>
113 + /// Gets the full path of the archive.
114 + /// </summary>
115 + /// <returns>The full path of the archive.</returns>
116 + public override string ToString()
117 + {
118 + return this.FullName;
119 + }
120 +
121 + /// <summary>
122 + /// Deletes the archive.
123 + /// </summary>
124 + public override void Delete()
125 + {
126 + File.Delete(this.FullName);
127 + }
128 +
129 + /// <summary>
130 + /// Copies an existing archive to another location.
131 + /// </summary>
132 + /// <param name="destFileName">The destination file path.</param>
133 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
134 + public void CopyTo(string destFileName)
135 + {
136 + File.Copy(this.FullName, destFileName);
137 + }
138 +
139 + /// <summary>
140 + /// Copies an existing archive to another location, optionally
141 + /// overwriting the destination file.
142 + /// </summary>
143 + /// <param name="destFileName">The destination file path.</param>
144 + /// <param name="overwrite">If true, the destination file will be
145 + /// overwritten if it exists.</param>
146 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
147 + public void CopyTo(string destFileName, bool overwrite)
148 + {
149 + File.Copy(this.FullName, destFileName, overwrite);
150 + }
151 +
152 + /// <summary>
153 + /// Moves an existing archive to another location.
154 + /// </summary>
155 + /// <param name="destFileName">The destination file path.</param>
156 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
157 + public void MoveTo(string destFileName)
158 + {
159 + File.Move(this.FullName, destFileName);
160 + this.FullPath = Path.GetFullPath(destFileName);
161 + }
162 +
163 + /// <summary>
164 + /// Checks if the archive contains a valid archive header.
165 + /// </summary>
166 + /// <returns>True if the file is a valid archive; false otherwise.</returns>
167 + public bool IsValid()
168 + {
169 + using (Stream stream = File.OpenRead(this.FullName))
170 + {
171 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
172 + {
173 + return compressionEngine.FindArchiveOffset(stream) >= 0;
174 + }
175 + }
176 + }
177 +
178 + /// <summary>
179 + /// Gets information about the files contained in the archive.
180 + /// </summary>
181 + /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
182 + /// containing information about a file in the archive.</returns>
183 + public IList<ArchiveFileInfo> GetFiles()
184 + {
185 + return this.InternalGetFiles((Predicate<string>) null);
186 + }
187 +
188 + /// <summary>
189 + /// Gets information about the certain files contained in the archive file.
190 + /// </summary>
191 + /// <param name="searchPattern">The search string, such as
192 + /// &quot;*.txt&quot;.</param>
193 + /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each containing
194 + /// information about a file in the archive.</returns>
195 + public IList<ArchiveFileInfo> GetFiles(string searchPattern)
196 + {
197 + if (searchPattern == null)
198 + {
199 + throw new ArgumentNullException("searchPattern");
200 + }
201 +
202 + string regexPattern = String.Format(
203 + CultureInfo.InvariantCulture,
204 + "^{0}$",
205 + Regex.Escape(searchPattern).Replace("\\*", ".*").Replace("\\?", "."));
206 + Regex regex = new Regex(
207 + regexPattern,
208 + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
209 +
210 + return this.InternalGetFiles(
211 + delegate(string match)
212 + {
213 + return regex.IsMatch(match);
214 + });
215 + }
216 +
217 + /// <summary>
218 + /// Extracts all files from an archive to a destination directory.
219 + /// </summary>
220 + /// <param name="destDirectory">Directory where the files are to be
221 + /// extracted.</param>
222 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
223 + public void Unpack(string destDirectory)
224 + {
225 + this.Unpack(destDirectory, null);
226 + }
227 +
228 + /// <summary>
229 + /// Extracts all files from an archive to a destination directory,
230 + /// optionally extracting only newer files.
231 + /// </summary>
232 + /// <param name="destDirectory">Directory where the files are to be
233 + /// extracted.</param>
234 + /// <param name="progressHandler">Handler for receiving progress
235 + /// information; this may be null if progress is not desired.</param>
236 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
237 + public void Unpack(
238 + string destDirectory,
239 + EventHandler<ArchiveProgressEventArgs> progressHandler)
240 + {
241 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
242 + {
243 + compressionEngine.Progress += progressHandler;
244 + ArchiveFileStreamContext streamContext =
245 + new ArchiveFileStreamContext(this.FullName, destDirectory, null);
246 + streamContext.EnableOffsetOpen = true;
247 + compressionEngine.Unpack(streamContext, null);
248 + }
249 + }
250 +
251 + /// <summary>
252 + /// Extracts a single file from the archive.
253 + /// </summary>
254 + /// <param name="fileName">The name of the file in the archive. Also
255 + /// includes the internal path of the file, if any. File name matching
256 + /// is case-insensitive.</param>
257 + /// <param name="destFileName">The path where the file is to be
258 + /// extracted on disk.</param>
259 + /// <remarks>If <paramref name="destFileName"/> already exists,
260 + /// it will be overwritten.</remarks>
261 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
262 + public void UnpackFile(string fileName, string destFileName)
263 + {
264 + if (fileName == null)
265 + {
266 + throw new ArgumentNullException("fileName");
267 + }
268 +
269 + if (destFileName == null)
270 + {
271 + throw new ArgumentNullException("destFileName");
272 + }
273 +
274 + this.UnpackFiles(
275 + new string[] { fileName },
276 + null,
277 + new string[] { destFileName });
278 + }
279 +
280 + /// <summary>
281 + /// Extracts multiple files from the archive.
282 + /// </summary>
283 + /// <param name="fileNames">The names of the files in the archive.
284 + /// Each name includes the internal path of the file, if any. File name
285 + /// matching is case-insensitive.</param>
286 + /// <param name="destDirectory">This parameter may be null, but if
287 + /// specified it is the root directory for any relative paths in
288 + /// <paramref name="destFileNames"/>.</param>
289 + /// <param name="destFileNames">The paths where the files are to be
290 + /// extracted on disk. If this parameter is null, the files will be
291 + /// extracted with the names from the archive.</param>
292 + /// <remarks>
293 + /// If any extracted files already exist on disk, they will be overwritten.
294 + /// <p>The <paramref name="destDirectory"/> and
295 + /// <paramref name="destFileNames"/> parameters cannot both be null.</p>
296 + /// </remarks>
297 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
298 + public void UnpackFiles(
299 + IList<string> fileNames,
300 + string destDirectory,
301 + IList<string> destFileNames)
302 + {
303 + this.UnpackFiles(fileNames, destDirectory, destFileNames, null);
304 + }
305 +
306 + /// <summary>
307 + /// Extracts multiple files from the archive, optionally extracting
308 + /// only newer files.
309 + /// </summary>
310 + /// <param name="fileNames">The names of the files in the archive.
311 + /// Each name includes the internal path of the file, if any. File name
312 + /// matching is case-insensitive.</param>
313 + /// <param name="destDirectory">This parameter may be null, but if
314 + /// specified it is the root directory for any relative paths in
315 + /// <paramref name="destFileNames"/>.</param>
316 + /// <param name="destFileNames">The paths where the files are to be
317 + /// extracted on disk. If this parameter is null, the files will be
318 + /// extracted with the names from the archive.</param>
319 + /// <param name="progressHandler">Handler for receiving progress information;
320 + /// this may be null if progress is not desired.</param>
321 + /// <remarks>
322 + /// If any extracted files already exist on disk, they will be overwritten.
323 + /// <p>The <paramref name="destDirectory"/> and
324 + /// <paramref name="destFileNames"/> parameters cannot both be null.</p>
325 + /// </remarks>
326 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
327 + public void UnpackFiles(
328 + IList<string> fileNames,
329 + string destDirectory,
330 + IList<string> destFileNames,
331 + EventHandler<ArchiveProgressEventArgs> progressHandler)
332 + {
333 + if (fileNames == null)
334 + {
335 + throw new ArgumentNullException("fileNames");
336 + }
337 +
338 + if (destFileNames == null)
339 + {
340 + if (destDirectory == null)
341 + {
342 + throw new ArgumentNullException("destFileNames");
343 + }
344 +
345 + destFileNames = fileNames;
346 + }
347 +
348 + if (destFileNames.Count != fileNames.Count)
349 + {
350 + throw new ArgumentOutOfRangeException("destFileNames");
351 + }
352 +
353 + IDictionary<string, string> files =
354 + ArchiveInfo.CreateStringDictionary(fileNames, destFileNames);
355 + this.UnpackFileSet(files, destDirectory, progressHandler);
356 + }
357 +
358 + /// <summary>
359 + /// Extracts multiple files from the archive.
360 + /// </summary>
361 + /// <param name="fileNames">A mapping from internal file paths to
362 + /// external file paths. Case-senstivity when matching internal paths
363 + /// depends on the IDictionary implementation.</param>
364 + /// <param name="destDirectory">This parameter may be null, but if
365 + /// specified it is the root directory for any relative external paths
366 + /// in <paramref name="fileNames"/>.</param>
367 + /// <remarks>
368 + /// If any extracted files already exist on disk, they will be overwritten.
369 + /// </remarks>
370 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
371 + public void UnpackFileSet(
372 + IDictionary<string, string> fileNames,
373 + string destDirectory)
374 + {
375 + this.UnpackFileSet(fileNames, destDirectory, null);
376 + }
377 +
378 + /// <summary>
379 + /// Extracts multiple files from the archive.
380 + /// </summary>
381 + /// <param name="fileNames">A mapping from internal file paths to
382 + /// external file paths. Case-senstivity when matching internal
383 + /// paths depends on the IDictionary implementation.</param>
384 + /// <param name="destDirectory">This parameter may be null, but if
385 + /// specified it is the root directory for any relative external
386 + /// paths in <paramref name="fileNames"/>.</param>
387 + /// <param name="progressHandler">Handler for receiving progress
388 + /// information; this may be null if progress is not desired.</param>
389 + /// <remarks>
390 + /// If any extracted files already exist on disk, they will be overwritten.
391 + /// </remarks>
392 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
393 + public void UnpackFileSet(
394 + IDictionary<string, string> fileNames,
395 + string destDirectory,
396 + EventHandler<ArchiveProgressEventArgs> progressHandler)
397 + {
398 + if (fileNames == null)
399 + {
400 + throw new ArgumentNullException("fileNames");
401 + }
402 +
403 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
404 + {
405 + compressionEngine.Progress += progressHandler;
406 + ArchiveFileStreamContext streamContext =
407 + new ArchiveFileStreamContext(this.FullName, destDirectory, fileNames);
408 + streamContext.EnableOffsetOpen = true;
409 + compressionEngine.Unpack(
410 + streamContext,
411 + delegate(string match)
412 + {
413 + return fileNames.ContainsKey(match);
414 + });
415 + }
416 + }
417 +
418 + /// <summary>
419 + /// Opens a file inside the archive for reading without actually
420 + /// extracting the file to disk.
421 + /// </summary>
422 + /// <param name="fileName">The name of the file in the archive. Also
423 + /// includes the internal path of the file, if any. File name matching
424 + /// is case-insensitive.</param>
425 + /// <returns>
426 + /// A stream for reading directly from the packed file. Like any stream
427 + /// this should be closed/disposed as soon as it is no longer needed.
428 + /// </returns>
429 + public Stream OpenRead(string fileName)
430 + {
431 + Stream archiveStream = File.OpenRead(this.FullName);
432 + CompressionEngine compressionEngine = this.CreateCompressionEngine();
433 + Stream fileStream = compressionEngine.Unpack(archiveStream, fileName);
434 +
435 + // Attach the archiveStream and compressionEngine to the
436 + // fileStream so they get disposed when the fileStream is disposed.
437 + return new CargoStream(fileStream, archiveStream, compressionEngine);
438 + }
439 +
440 + /// <summary>
441 + /// Opens a file inside the archive for reading text with UTF-8 encoding
442 + /// without actually extracting the file to disk.
443 + /// </summary>
444 + /// <param name="fileName">The name of the file in the archive. Also
445 + /// includes the internal path of the file, if any. File name matching
446 + /// is case-insensitive.</param>
447 + /// <returns>
448 + /// A reader for reading text directly from the packed file. Like any reader
449 + /// this should be closed/disposed as soon as it is no longer needed.
450 + /// </returns>
451 + /// <remarks>
452 + /// To open an archived text file with different encoding, use the
453 + /// <see cref="OpenRead" /> method and pass the returned stream to one of
454 + /// the <see cref="StreamReader" /> constructor overloads.
455 + /// </remarks>
456 + public StreamReader OpenText(string fileName)
457 + {
458 + return new StreamReader(this.OpenRead(fileName));
459 + }
460 +
461 + /// <summary>
462 + /// Compresses all files in a directory into the archive.
463 + /// Does not include subdirectories.
464 + /// </summary>
465 + /// <param name="sourceDirectory">The directory containing the
466 + /// files to be included.</param>
467 + /// <remarks>
468 + /// Uses maximum compression level.
469 + /// </remarks>
470 + public void Pack(string sourceDirectory)
471 + {
472 + this.Pack(sourceDirectory, false, CompressionLevel.Max, null);
473 + }
474 +
475 + /// <summary>
476 + /// Compresses all files in a directory into the archive, optionally
477 + /// including subdirectories.
478 + /// </summary>
479 + /// <param name="sourceDirectory">This is the root directory
480 + /// for to pack all files.</param>
481 + /// <param name="includeSubdirectories">If true, recursively include
482 + /// files in subdirectories.</param>
483 + /// <param name="compLevel">The compression level used when creating
484 + /// the archive.</param>
485 + /// <param name="progressHandler">Handler for receiving progress information;
486 + /// this may be null if progress is not desired.</param>
487 + /// <remarks>
488 + /// The files are stored in the archive using their relative file paths in
489 + /// the directory tree, if supported by the archive file format.
490 + /// </remarks>
491 + public void Pack(
492 + string sourceDirectory,
493 + bool includeSubdirectories,
494 + CompressionLevel compLevel,
495 + EventHandler<ArchiveProgressEventArgs> progressHandler)
496 + {
497 + IList<string> files = this.GetRelativeFilePathsInDirectoryTree(
498 + sourceDirectory, includeSubdirectories);
499 + this.PackFiles(sourceDirectory, files, files, compLevel, progressHandler);
500 + }
501 +
502 + /// <summary>
503 + /// Compresses files into the archive, specifying the names used to
504 + /// store the files in the archive.
505 + /// </summary>
506 + /// <param name="sourceDirectory">This parameter may be null, but
507 + /// if specified it is the root directory
508 + /// for any relative paths in <paramref name="sourceFileNames"/>.</param>
509 + /// <param name="sourceFileNames">The list of files to be included in
510 + /// the archive.</param>
511 + /// <param name="fileNames">The names of the files as they are stored
512 + /// in the archive. Each name
513 + /// includes the internal path of the file, if any. This parameter may
514 + /// be null, in which case the files are stored in the archive with their
515 + /// source file names and no path information.</param>
516 + /// <remarks>
517 + /// Uses maximum compression level.
518 + /// <p>Duplicate items in the <paramref name="fileNames"/> array will cause
519 + /// an <see cref="ArchiveException"/>.</p>
520 + /// </remarks>
521 + public void PackFiles(
522 + string sourceDirectory,
523 + IList<string> sourceFileNames,
524 + IList<string> fileNames)
525 + {
526 + this.PackFiles(
527 + sourceDirectory,
528 + sourceFileNames,
529 + fileNames,
530 + CompressionLevel.Max,
531 + null);
532 + }
533 +
534 + /// <summary>
535 + /// Compresses files into the archive, specifying the names used to
536 + /// store the files in the archive.
537 + /// </summary>
538 + /// <param name="sourceDirectory">This parameter may be null, but if
539 + /// specified it is the root directory
540 + /// for any relative paths in <paramref name="sourceFileNames"/>.</param>
541 + /// <param name="sourceFileNames">The list of files to be included in
542 + /// the archive.</param>
543 + /// <param name="fileNames">The names of the files as they are stored in
544 + /// the archive. Each name includes the internal path of the file, if any.
545 + /// This parameter may be null, in which case the files are stored in the
546 + /// archive with their source file names and no path information.</param>
547 + /// <param name="compLevel">The compression level used when creating the
548 + /// archive.</param>
549 + /// <param name="progressHandler">Handler for receiving progress information;
550 + /// this may be null if progress is not desired.</param>
551 + /// <remarks>
552 + /// Duplicate items in the <paramref name="fileNames"/> array will cause
553 + /// an <see cref="ArchiveException"/>.
554 + /// </remarks>
555 + public void PackFiles(
556 + string sourceDirectory,
557 + IList<string> sourceFileNames,
558 + IList<string> fileNames,
559 + CompressionLevel compLevel,
560 + EventHandler<ArchiveProgressEventArgs> progressHandler)
561 + {
562 + if (sourceFileNames == null)
563 + {
564 + throw new ArgumentNullException("sourceFileNames");
565 + }
566 +
567 + if (fileNames == null)
568 + {
569 + string[] fileNamesArray = new string[sourceFileNames.Count];
570 + for (int i = 0; i < sourceFileNames.Count; i++)
571 + {
572 + fileNamesArray[i] = Path.GetFileName(sourceFileNames[i]);
573 + }
574 +
575 + fileNames = fileNamesArray;
576 + }
577 + else if (fileNames.Count != sourceFileNames.Count)
578 + {
579 + throw new ArgumentOutOfRangeException("fileNames");
580 + }
581 +
582 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
583 + {
584 + compressionEngine.Progress += progressHandler;
585 + IDictionary<string, string> contextFiles =
586 + ArchiveInfo.CreateStringDictionary(fileNames, sourceFileNames);
587 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
588 + this.FullName, sourceDirectory, contextFiles);
589 + streamContext.EnableOffsetOpen = true;
590 + compressionEngine.CompressionLevel = compLevel;
591 + compressionEngine.Pack(streamContext, fileNames);
592 + }
593 + }
594 +
595 + /// <summary>
596 + /// Compresses files into the archive, specifying the names used
597 + /// to store the files in the archive.
598 + /// </summary>
599 + /// <param name="sourceDirectory">This parameter may be null, but if
600 + /// specified it is the root directory
601 + /// for any relative paths in <paramref name="fileNames"/>.</param>
602 + /// <param name="fileNames">A mapping from internal file paths to
603 + /// external file paths.</param>
604 + /// <remarks>
605 + /// Uses maximum compression level.
606 + /// </remarks>
607 + public void PackFileSet(
608 + string sourceDirectory,
609 + IDictionary<string, string> fileNames)
610 + {
611 + this.PackFileSet(sourceDirectory, fileNames, CompressionLevel.Max, null);
612 + }
613 +
614 + /// <summary>
615 + /// Compresses files into the archive, specifying the names used to
616 + /// store the files in the archive.
617 + /// </summary>
618 + /// <param name="sourceDirectory">This parameter may be null, but if
619 + /// specified it is the root directory
620 + /// for any relative paths in <paramref name="fileNames"/>.</param>
621 + /// <param name="fileNames">A mapping from internal file paths to
622 + /// external file paths.</param>
623 + /// <param name="compLevel">The compression level used when creating
624 + /// the archive.</param>
625 + /// <param name="progressHandler">Handler for receiving progress information;
626 + /// this may be null if progress is not desired.</param>
627 + public void PackFileSet(
628 + string sourceDirectory,
629 + IDictionary<string, string> fileNames,
630 + CompressionLevel compLevel,
631 + EventHandler<ArchiveProgressEventArgs> progressHandler)
632 + {
633 + if (fileNames == null)
634 + {
635 + throw new ArgumentNullException("fileNames");
636 + }
637 +
638 + string[] fileNamesArray = new string[fileNames.Count];
639 + fileNames.Keys.CopyTo(fileNamesArray, 0);
640 +
641 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
642 + {
643 + compressionEngine.Progress += progressHandler;
644 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
645 + this.FullName, sourceDirectory, fileNames);
646 + streamContext.EnableOffsetOpen = true;
647 + compressionEngine.CompressionLevel = compLevel;
648 + compressionEngine.Pack(streamContext, fileNamesArray);
649 + }
650 + }
651 +
652 + /// <summary>
653 + /// Given a directory, gets the relative paths of all files in the
654 + /// directory, optionally including all subdirectories.
655 + /// </summary>
656 + /// <param name="dir">The directory to search.</param>
657 + /// <param name="includeSubdirectories">True to include subdirectories
658 + /// in the search.</param>
659 + /// <returns>A list of file paths relative to the directory.</returns>
660 + internal IList<string> GetRelativeFilePathsInDirectoryTree(
661 + string dir, bool includeSubdirectories)
662 + {
663 + IList<string> fileList = new List<string>();
664 + this.RecursiveGetRelativeFilePathsInDirectoryTree(
665 + dir, String.Empty, includeSubdirectories, fileList);
666 + return fileList;
667 + }
668 +
669 + /// <summary>
670 + /// Retrieves information about one file from this archive.
671 + /// </summary>
672 + /// <param name="path">Path of the file in the archive.</param>
673 + /// <returns>File information, or null if the file was not found
674 + /// in the archive.</returns>
675 + internal ArchiveFileInfo GetFile(string path)
676 + {
677 + IList<ArchiveFileInfo> files = this.InternalGetFiles(
678 + delegate(string match)
679 + {
680 + return String.Compare(
681 + match, path, true, CultureInfo.InvariantCulture) == 0;
682 + });
683 + return (files != null && files.Count > 0 ? files[0] : null);
684 + }
685 +
686 + /// <summary>
687 + /// Creates a compression engine that does the low-level work for
688 + /// this object.
689 + /// </summary>
690 + /// <returns>A new compression engine instance that matches the specific
691 + /// subclass of archive.</returns>
692 + /// <remarks>
693 + /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
694 + /// immediately after use.
695 + /// </remarks>
696 + protected abstract CompressionEngine CreateCompressionEngine();
697 +
698 + /// <summary>
699 + /// Creates a case-insensitive dictionary mapping from one list of
700 + /// strings to the other.
701 + /// </summary>
702 + /// <param name="keys">List of keys.</param>
703 + /// <param name="values">List of values that are mapped 1-to-1 to
704 + /// the keys.</param>
705 + /// <returns>A filled dictionary of the strings.</returns>
706 + private static IDictionary<string, string> CreateStringDictionary(
707 + IList<string> keys, IList<string> values)
708 + {
709 + IDictionary<string, string> stringDict =
710 + new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
711 + for (int i = 0; i < keys.Count; i++)
712 + {
713 + stringDict.Add(keys[i], values[i]);
714 + }
715 +
716 + return stringDict;
717 + }
718 +
719 + /// <summary>
720 + /// Recursive-descent helper function for
721 + /// GetRelativeFilePathsInDirectoryTree.
722 + /// </summary>
723 + /// <param name="dir">The root directory of the search.</param>
724 + /// <param name="relativeDir">The relative directory to be
725 + /// processed now.</param>
726 + /// <param name="includeSubdirectories">True to descend into
727 + /// subdirectories.</param>
728 + /// <param name="fileList">List of files found so far.</param>
729 + private void RecursiveGetRelativeFilePathsInDirectoryTree(
730 + string dir,
731 + string relativeDir,
732 + bool includeSubdirectories,
733 + IList<string> fileList)
734 + {
735 + foreach (string file in System.IO.Directory.GetFiles(dir))
736 + {
737 + string fileName = Path.GetFileName(file);
738 + fileList.Add(Path.Combine(relativeDir, fileName));
739 + }
740 +
741 + if (includeSubdirectories)
742 + {
743 + foreach (string subDir in System.IO.Directory.GetDirectories(dir))
744 + {
745 + string subDirName = Path.GetFileName(subDir);
746 + this.RecursiveGetRelativeFilePathsInDirectoryTree(
747 + Path.Combine(dir, subDirName),
748 + Path.Combine(relativeDir, subDirName),
749 + includeSubdirectories,
750 + fileList);
751 + }
752 + }
753 + }
754 +
755 + /// <summary>
756 + /// Uses a CompressionEngine to get ArchiveFileInfo objects from this
757 + /// archive, and then associates them with this ArchiveInfo instance.
758 + /// </summary>
759 + /// <param name="fileFilter">Optional predicate that can determine
760 + /// which files to process.</param>
761 + /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
762 + /// containing information about a file in the archive.</returns>
763 + private IList<ArchiveFileInfo> InternalGetFiles(Predicate<string> fileFilter)
764 + {
765 + using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
766 + {
767 + ArchiveFileStreamContext streamContext =
768 + new ArchiveFileStreamContext(this.FullName, null, null);
769 + streamContext.EnableOffsetOpen = true;
770 + IList<ArchiveFileInfo> files =
771 + compressionEngine.GetFileInfo(streamContext, fileFilter);
772 + for (int i = 0; i < files.Count; i++)
773 + {
774 + files[i].Archive = this;
775 + }
776 +
777 + return files;
778 + }
779 + }
780 + }
781 +}
src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs new
+307
@@ -0,0 +1,307 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 +using System;
6 +using System.Collections.Generic;
7 +using System.Text;
8 +
9 + /// <summary>
10 + /// Contains the data reported in an archive progress event.
11 + /// </summary>
12 + public class ArchiveProgressEventArgs : EventArgs
13 + {
14 + private ArchiveProgressType progressType;
15 +
16 + private string currentFileName;
17 + private int currentFileNumber;
18 + private int totalFiles;
19 + private long currentFileBytesProcessed;
20 + private long currentFileTotalBytes;
21 +
22 + private string currentArchiveName;
23 + private short currentArchiveNumber;
24 + private short totalArchives;
25 + private long currentArchiveBytesProcessed;
26 + private long currentArchiveTotalBytes;
27 +
28 + private long fileBytesProcessed;
29 + private long totalFileBytes;
30 +
31 + /// <summary>
32 + /// Creates a new ArchiveProgressEventArgs object from specified event parameters.
33 + /// </summary>
34 + /// <param name="progressType">type of status message</param>
35 + /// <param name="currentFileName">name of the file being processed</param>
36 + /// <param name="currentFileNumber">number of the current file being processed</param>
37 + /// <param name="totalFiles">total number of files to be processed</param>
38 + /// <param name="currentFileBytesProcessed">number of bytes processed so far when compressing or extracting a file</param>
39 + /// <param name="currentFileTotalBytes">total number of bytes in the current file</param>
40 + /// <param name="currentArchiveName">name of the current Archive</param>
41 + /// <param name="currentArchiveNumber">current Archive number, when processing a chained set of Archives</param>
42 + /// <param name="totalArchives">total number of Archives in a chained set</param>
43 + /// <param name="currentArchiveBytesProcessed">number of compressed bytes processed so far during an extraction</param>
44 + /// <param name="currentArchiveTotalBytes">total number of compressed bytes to be processed during an extraction</param>
45 + /// <param name="fileBytesProcessed">number of uncompressed file bytes processed so far</param>
46 + /// <param name="totalFileBytes">total number of uncompressed file bytes to be processed</param>
47 + public ArchiveProgressEventArgs(
48 + ArchiveProgressType progressType,
49 + string currentFileName,
50 + int currentFileNumber,
51 + int totalFiles,
52 + long currentFileBytesProcessed,
53 + long currentFileTotalBytes,
54 + string currentArchiveName,
55 + int currentArchiveNumber,
56 + int totalArchives,
57 + long currentArchiveBytesProcessed,
58 + long currentArchiveTotalBytes,
59 + long fileBytesProcessed,
60 + long totalFileBytes)
61 + {
62 + this.progressType = progressType;
63 + this.currentFileName = currentFileName;
64 + this.currentFileNumber = currentFileNumber;
65 + this.totalFiles = totalFiles;
66 + this.currentFileBytesProcessed = currentFileBytesProcessed;
67 + this.currentFileTotalBytes = currentFileTotalBytes;
68 + this.currentArchiveName = currentArchiveName;
69 + this.currentArchiveNumber = (short) currentArchiveNumber;
70 + this.totalArchives = (short) totalArchives;
71 + this.currentArchiveBytesProcessed = currentArchiveBytesProcessed;
72 + this.currentArchiveTotalBytes = currentArchiveTotalBytes;
73 + this.fileBytesProcessed = fileBytesProcessed;
74 + this.totalFileBytes = totalFileBytes;
75 + }
76 +
77 + /// <summary>
78 + /// Gets the type of status message.
79 + /// </summary>
80 + /// <value>A <see cref="ArchiveProgressType"/> value indicating what type of progress event occurred.</value>
81 + /// <remarks>
82 + /// The handler may choose to ignore some types of progress events.
83 + /// For example, if the handler will only list each file as it is
84 + /// compressed/extracted, it can ignore events that
85 + /// are not of type <see cref="ArchiveProgressType.FinishFile"/>.
86 + /// </remarks>
87 + public ArchiveProgressType ProgressType
88 + {
89 + get
90 + {
91 + return this.progressType;
92 + }
93 + }
94 +
95 + /// <summary>
96 + /// Gets the name of the file being processed. (The name of the file within the Archive; not the external
97 + /// file path.) Also includes the internal path of the file, if any. Valid for
98 + /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
99 + /// and <see cref="ArchiveProgressType.FinishFile"/> messages.
100 + /// </summary>
101 + /// <value>The name of the file currently being processed, or null if processing
102 + /// is currently at the stream or archive level.</value>
103 + public string CurrentFileName
104 + {
105 + get
106 + {
107 + return this.currentFileName;
108 + }
109 + }
110 +
111 + /// <summary>
112 + /// Gets the number of the current file being processed. The first file is number 0, and the last file
113 + /// is <see cref="TotalFiles"/>-1. Valid for <see cref="ArchiveProgressType.StartFile"/>,
114 + /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
115 + /// </summary>
116 + /// <value>The number of the file currently being processed, or the most recent
117 + /// file processed if processing is currently at the stream or archive level.</value>
118 + public int CurrentFileNumber
119 + {
120 + get
121 + {
122 + return this.currentFileNumber;
123 + }
124 + }
125 +
126 + /// <summary>
127 + /// Gets the total number of files to be processed. Valid for all message types.
128 + /// </summary>
129 + /// <value>The total number of files to be processed that are known so far.</value>
130 + public int TotalFiles
131 + {
132 + get
133 + {
134 + return this.totalFiles;
135 + }
136 + }
137 +
138 + /// <summary>
139 + /// Gets the number of bytes processed so far when compressing or extracting a file. Valid for
140 + /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
141 + /// and <see cref="ArchiveProgressType.FinishFile"/> messages.
142 + /// </summary>
143 + /// <value>The number of uncompressed bytes processed so far for the current file,
144 + /// or 0 if processing is currently at the stream or archive level.</value>
145 + public long CurrentFileBytesProcessed
146 + {
147 + get
148 + {
149 + return this.currentFileBytesProcessed;
150 + }
151 + }
152 +
153 + /// <summary>
154 + /// Gets the total number of bytes in the current file. Valid for <see cref="ArchiveProgressType.StartFile"/>,
155 + /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
156 + /// </summary>
157 + /// <value>The uncompressed size of the current file being processed,
158 + /// or 0 if processing is currently at the stream or archive level.</value>
159 + public long CurrentFileTotalBytes
160 + {
161 + get
162 + {
163 + return this.currentFileTotalBytes;
164 + }
165 + }
166 +
167 + /// <summary>
168 + /// Gets the name of the current archive. Not necessarily the name of the archive on disk.
169 + /// Valid for all message types.
170 + /// </summary>
171 + /// <value>The name of the current archive, or an empty string if no name was specified.</value>
172 + public string CurrentArchiveName
173 + {
174 + get
175 + {
176 + return this.currentArchiveName;
177 + }
178 + }
179 +
180 + /// <summary>
181 + /// Gets the current archive number, when processing a chained set of archives. Valid for all message types.
182 + /// </summary>
183 + /// <value>The number of the current archive.</value>
184 + /// <remarks>The first archive is number 0, and the last archive is
185 + /// <see cref="TotalArchives"/>-1.</remarks>
186 + public int CurrentArchiveNumber
187 + {
188 + get
189 + {
190 + return this.currentArchiveNumber;
191 + }
192 + }
193 +
194 + /// <summary>
195 + /// Gets the total number of known archives in a chained set. Valid for all message types.
196 + /// </summary>
197 + /// <value>The total number of known archives in a chained set.</value>
198 + /// <remarks>
199 + /// When using the compression option to auto-split into multiple archives based on data size,
200 + /// this value will not be accurate until the end.
201 + /// </remarks>
202 + public int TotalArchives
203 + {
204 + get
205 + {
206 + return this.totalArchives;
207 + }
208 + }
209 +
210 + /// <summary>
211 + /// Gets the number of compressed bytes processed so far during extraction
212 + /// of the current archive. Valid for all extraction messages.
213 + /// </summary>
214 + /// <value>The number of compressed bytes processed so far during extraction
215 + /// of the current archive.</value>
216 + public long CurrentArchiveBytesProcessed
217 + {
218 + get
219 + {
220 + return this.currentArchiveBytesProcessed;
221 + }
222 + }
223 +
224 + /// <summary>
225 + /// Gets the total number of compressed bytes to be processed during extraction
226 + /// of the current archive. Valid for all extraction messages.
227 + /// </summary>
228 + /// <value>The total number of compressed bytes to be processed during extraction
229 + /// of the current archive.</value>
230 + public long CurrentArchiveTotalBytes
231 + {
232 + get
233 + {
234 + return this.currentArchiveTotalBytes;
235 + }
236 + }
237 +
238 + /// <summary>
239 + /// Gets the number of uncompressed bytes processed so far among all files. Valid for all message types.
240 + /// </summary>
241 + /// <value>The number of uncompressed file bytes processed so far among all files.</value>
242 + /// <remarks>
243 + /// When compared to <see cref="TotalFileBytes"/>, this can be used as a measure of overall progress.
244 + /// </remarks>
245 + public long FileBytesProcessed
246 + {
247 + get
248 + {
249 + return this.fileBytesProcessed;
250 + }
251 + }
252 +
253 + /// <summary>
254 + /// Gets the total number of uncompressed file bytes to be processed. Valid for all message types.
255 + /// </summary>
256 + /// <value>The total number of uncompressed bytes to be processed among all files.</value>
257 + public long TotalFileBytes
258 + {
259 + get
260 + {
261 + return this.totalFileBytes;
262 + }
263 + }
264 +
265 +#if DEBUG
266 +
267 + /// <summary>
268 + /// Creates a string representation of the progress event.
269 + /// </summary>
270 + /// <returns>a listing of all event parameters and values</returns>
271 + public override string ToString()
272 + {
273 + string formatString =
274 + "{0}\n" +
275 + "\t CurrentFileName = {1}\n" +
276 + "\t CurrentFileNumber = {2}\n" +
277 + "\t TotalFiles = {3}\n" +
278 + "\t CurrentFileBytesProcessed = {4}\n" +
279 + "\t CurrentFileTotalBytes = {5}\n" +
280 + "\t CurrentArchiveName = {6}\n" +
281 + "\t CurrentArchiveNumber = {7}\n" +
282 + "\t TotalArchives = {8}\n" +
283 + "\t CurrentArchiveBytesProcessed = {9}\n" +
284 + "\t CurrentArchiveTotalBytes = {10}\n" +
285 + "\t FileBytesProcessed = {11}\n" +
286 + "\t TotalFileBytes = {12}\n";
287 + return String.Format(
288 + System.Globalization.CultureInfo.InvariantCulture,
289 + formatString,
290 + this.ProgressType,
291 + this.CurrentFileName,
292 + this.CurrentFileNumber,
293 + this.TotalFiles,
294 + this.CurrentFileBytesProcessed,
295 + this.CurrentFileTotalBytes,
296 + this.CurrentArchiveName,
297 + this.CurrentArchiveNumber,
298 + this.TotalArchives,
299 + this.CurrentArchiveBytesProcessed,
300 + this.CurrentArchiveTotalBytes,
301 + this.FileBytesProcessed,
302 + this.TotalFileBytes);
303 + }
304 +
305 +#endif
306 + }
307 +}
src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs new
+69
@@ -0,0 +1,69 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 +using System;
6 +using System.Collections.Generic;
7 +using System.Text;
8 +
9 + /// <summary>
10 + /// The type of progress event.
11 + /// </summary>
12 + /// <remarks>
13 + /// <p>PACKING EXAMPLE: The following sequence of events might be received when
14 + /// extracting a simple archive file with 2 files.</p>
15 + /// <list type="table">
16 + /// <listheader><term>Message Type</term><description>Description</description></listheader>
17 + /// <item><term>StartArchive</term> <description>Begin extracting archive</description></item>
18 + /// <item><term>StartFile</term> <description>Begin extracting first file</description></item>
19 + /// <item><term>PartialFile</term> <description>Extracting first file</description></item>
20 + /// <item><term>PartialFile</term> <description>Extracting first file</description></item>
21 + /// <item><term>FinishFile</term> <description>Finished extracting first file</description></item>
22 + /// <item><term>StartFile</term> <description>Begin extracting second file</description></item>
23 + /// <item><term>PartialFile</term> <description>Extracting second file</description></item>
24 + /// <item><term>FinishFile</term> <description>Finished extracting second file</description></item>
25 + /// <item><term>FinishArchive</term><description>Finished extracting archive</description></item>
26 + /// </list>
27 + /// <p></p>
28 + /// <p>UNPACKING EXAMPLE: Packing 3 files into 2 archive chunks, where the second file is
29 + /// continued to the second archive chunk.</p>
30 + /// <list type="table">
31 + /// <listheader><term>Message Type</term><description>Description</description></listheader>
32 + /// <item><term>StartFile</term> <description>Begin compressing first file</description></item>
33 + /// <item><term>FinishFile</term> <description>Finished compressing first file</description></item>
34 + /// <item><term>StartFile</term> <description>Begin compressing second file</description></item>
35 + /// <item><term>PartialFile</term> <description>Compressing second file</description></item>
36 + /// <item><term>PartialFile</term> <description>Compressing second file</description></item>
37 + /// <item><term>FinishFile</term> <description>Finished compressing second file</description></item>
38 + /// <item><term>StartArchive</term> <description>Begin writing first archive</description></item>
39 + /// <item><term>PartialArchive</term><description>Writing first archive</description></item>
40 + /// <item><term>FinishArchive</term> <description>Finished writing first archive</description></item>
41 + /// <item><term>StartFile</term> <description>Begin compressing third file</description></item>
42 + /// <item><term>PartialFile</term> <description>Compressing third file</description></item>
43 + /// <item><term>FinishFile</term> <description>Finished compressing third file</description></item>
44 + /// <item><term>StartArchive</term> <description>Begin writing second archive</description></item>
45 + /// <item><term>PartialArchive</term><description>Writing second archive</description></item>
46 + /// <item><term>FinishArchive</term> <description>Finished writing second archive</description></item>
47 + /// </list>
48 + /// </remarks>
49 + public enum ArchiveProgressType : int
50 + {
51 + /// <summary>Status message before beginning the packing or unpacking an individual file.</summary>
52 + StartFile,
53 +
54 + /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking a file.</summary>
55 + PartialFile,
56 +
57 + /// <summary>Status message after completion of the packing or unpacking an individual file.</summary>
58 + FinishFile,
59 +
60 + /// <summary>Status message before beginning the packing or unpacking an archive.</summary>
61 + StartArchive,
62 +
63 + /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking an archiv.</summary>
64 + PartialArchive,
65 +
66 + /// <summary>Status message after completion of the packing or unpacking of an archive.</summary>
67 + FinishArchive,
68 + }
69 +}
src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs new
+90
@@ -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 +
3 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Diagnostics.CodeAnalysis;
8 +
9 + /// <summary>
10 + /// Stream context used to extract a single file from an archive into a memory stream.
11 + /// </summary>
12 + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
13 + public class BasicUnpackStreamContext : IUnpackStreamContext
14 + {
15 + private Stream archiveStream;
16 + private Stream fileStream;
17 +
18 + /// <summary>
19 + /// Creates a new BasicExtractStreamContext that reads from the specified archive stream.
20 + /// </summary>
21 + /// <param name="archiveStream">Archive stream to read from.</param>
22 + public BasicUnpackStreamContext(Stream archiveStream)
23 + {
24 + this.archiveStream = archiveStream;
25 + }
26 +
27 + /// <summary>
28 + /// Gets the stream for the extracted file, or null if no file was extracted.
29 + /// </summary>
30 + public Stream FileStream
31 + {
32 + get
33 + {
34 + return this.fileStream;
35 + }
36 + }
37 +
38 + /// <summary>
39 + /// Opens the archive stream for reading. Returns a DuplicateStream instance,
40 + /// so the stream may be virtually opened multiple times.
41 + /// </summary>
42 + /// <param name="archiveNumber">The archive number to open (ignored; 0 is assumed).</param>
43 + /// <param name="archiveName">The name of the archive being opened.</param>
44 + /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
45 + /// <returns>A stream from which archive bytes are read.</returns>
46 + public Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine)
47 + {
48 + return new DuplicateStream(this.archiveStream);
49 + }
50 +
51 + /// <summary>
52 + /// Does *not* close the stream. The archive stream should be managed by
53 + /// the code that invokes the archive extraction.
54 + /// </summary>
55 + /// <param name="archiveNumber">The archive number of the stream to close.</param>
56 + /// <param name="archiveName">The name of the archive being closed.</param>
57 + /// <param name="stream">The stream being closed.</param>
58 + public void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream)
59 + {
60 + // Do nothing.
61 + }
62 +
63 + /// <summary>
64 + /// Opens a stream for writing extracted file bytes. The returned stream is a MemoryStream
65 + /// instance, so the file is extracted straight into memory.
66 + /// </summary>
67 + /// <param name="path">Path of the file within the archive.</param>
68 + /// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
69 + /// <param name="lastWriteTime">The last write time of the file.</param>
70 + /// <returns>A stream where extracted file bytes are to be written.</returns>
71 + public Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime)
72 + {
73 + this.fileStream = new MemoryStream(new byte[fileSize], 0, (int) fileSize, true, true);
74 + return this.fileStream;
75 + }
76 +
77 + /// <summary>
78 + /// Does *not* close the file stream. The file stream is saved in memory so it can
79 + /// be read later.
80 + /// </summary>
81 + /// <param name="path">Path of the file within the archive.</param>
82 + /// <param name="stream">The file stream to be closed.</param>
83 + /// <param name="attributes">The attributes of the extracted file.</param>
84 + /// <param name="lastWriteTime">The last write time of the file.</param>
85 + public void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime)
86 + {
87 + // Do nothing.
88 + }
89 + }
90 +}
src/dtf/WixToolset.Dtf.Compression/CargoStream.cs new
+192
@@ -0,0 +1,192 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.IO;
8 +
9 + /// <summary>
10 + /// Wraps a source stream and carries additional items that are disposed when the stream is closed.
11 + /// </summary>
12 + public class CargoStream : Stream
13 + {
14 + private Stream source;
15 + private List<IDisposable> cargo;
16 +
17 + /// <summary>
18 + /// Creates a new a cargo stream.
19 + /// </summary>
20 + /// <param name="source">source of the stream</param>
21 + /// <param name="cargo">List of additional items that are disposed when the stream is closed.
22 + /// The order of the list is the order in which the items are disposed.</param>
23 + public CargoStream(Stream source, params IDisposable[] cargo)
24 + {
25 + if (source == null)
26 + {
27 + throw new ArgumentNullException("source");
28 + }
29 +
30 + this.source = source;
31 + this.cargo = new List<IDisposable>(cargo);
32 + }
33 +
34 + /// <summary>
35 + /// Gets the source stream of the cargo stream.
36 + /// </summary>
37 + public Stream Source
38 + {
39 + get
40 + {
41 + return this.source;
42 + }
43 + }
44 +
45 + /// <summary>
46 + /// Gets the list of additional items that are disposed when the stream is closed.
47 + /// The order of the list is the order in which the items are disposed. The contents can be modified any time.
48 + /// </summary>
49 + public IList<IDisposable> Cargo
50 + {
51 + get
52 + {
53 + return this.cargo;
54 + }
55 + }
56 +
57 + /// <summary>
58 + /// Gets a value indicating whether the source stream supports reading.
59 + /// </summary>
60 + /// <value>true if the stream supports reading; otherwise, false.</value>
61 + public override bool CanRead
62 + {
63 + get
64 + {
65 + return this.source.CanRead;
66 + }
67 + }
68 +
69 + /// <summary>
70 + /// Gets a value indicating whether the source stream supports writing.
71 + /// </summary>
72 + /// <value>true if the stream supports writing; otherwise, false.</value>
73 + public override bool CanWrite
74 + {
75 + get
76 + {
77 + return this.source.CanWrite;
78 + }
79 + }
80 +
81 + /// <summary>
82 + /// Gets a value indicating whether the source stream supports seeking.
83 + /// </summary>
84 + /// <value>true if the stream supports seeking; otherwise, false.</value>
85 + public override bool CanSeek
86 + {
87 + get
88 + {
89 + return this.source.CanSeek;
90 + }
91 + }
92 +
93 + /// <summary>
94 + /// Gets the length of the source stream.
95 + /// </summary>
96 + public override long Length
97 + {
98 + get
99 + {
100 + return this.source.Length;
101 + }
102 + }
103 +
104 + /// <summary>
105 + /// Gets or sets the position of the source stream.
106 + /// </summary>
107 + public override long Position
108 + {
109 + get
110 + {
111 + return this.source.Position;
112 + }
113 +
114 + set
115 + {
116 + this.source.Position = value;
117 + }
118 + }
119 +
120 + /// <summary>
121 + /// Flushes the source stream.
122 + /// </summary>
123 + public override void Flush()
124 + {
125 + this.source.Flush();
126 + }
127 +
128 + /// <summary>
129 + /// Sets the length of the source stream.
130 + /// </summary>
131 + /// <param name="value">The desired length of the stream in bytes.</param>
132 + public override void SetLength(long value)
133 + {
134 + this.source.SetLength(value);
135 + }
136 +
137 + /// <summary>
138 + /// Closes the source stream and also closes the additional objects that are carried.
139 + /// </summary>
140 + public override void Close()
141 + {
142 + this.source.Close();
143 +
144 + foreach (IDisposable cargoObject in this.cargo)
145 + {
146 + cargoObject.Dispose();
147 + }
148 + }
149 +
150 + /// <summary>
151 + /// Reads from the source stream.
152 + /// </summary>
153 + /// <param name="buffer">An array of bytes. When this method returns, the buffer
154 + /// contains the specified byte array with the values between offset and
155 + /// (offset + count - 1) replaced by the bytes read from the source.</param>
156 + /// <param name="offset">The zero-based byte offset in buffer at which to begin
157 + /// storing the data read from the stream.</param>
158 + /// <param name="count">The maximum number of bytes to be read from the stream.</param>
159 + /// <returns>The total number of bytes read into the buffer. This can be less
160 + /// than the number of bytes requested if that many bytes are not currently available,
161 + /// or zero (0) if the end of the stream has been reached.</returns>
162 + public override int Read(byte[] buffer, int offset, int count)
163 + {
164 + return this.source.Read(buffer, offset, count);
165 + }
166 +
167 + /// <summary>
168 + /// Writes to the source stream.
169 + /// </summary>
170 + /// <param name="buffer">An array of bytes. This method copies count
171 + /// bytes from buffer to the stream.</param>
172 + /// <param name="offset">The zero-based byte offset in buffer at which
173 + /// to begin copying bytes to the stream.</param>
174 + /// <param name="count">The number of bytes to be written to the stream.</param>
175 + public override void Write(byte[] buffer, int offset, int count)
176 + {
177 + this.source.Write(buffer, offset, count);
178 + }
179 +
180 + /// <summary>
181 + /// Changes the position of the source stream.
182 + /// </summary>
183 + /// <param name="offset">A byte offset relative to the origin parameter.</param>
184 + /// <param name="origin">A value of type SeekOrigin indicating the reference
185 + /// point used to obtain the new position.</param>
186 + /// <returns>The new position within the stream.</returns>
187 + public override long Seek(long offset, SeekOrigin origin)
188 + {
189 + return this.source.Seek(offset, origin);
190 + }
191 + }
192 +}
src/dtf/WixToolset.Dtf.Compression/Compression.cd new
+175
@@ -0,0 +1,175 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<ClassDiagram MajorVersion="1" MinorVersion="1">
3 + <Comment CommentText="File-based classes">
4 + <Position X="2.35" Y="1.442" Height="0.408" Width="0.783" />
5 + </Comment>
6 + <Comment CommentText="Stream-based classes">
7 + <Position X="9.649" Y="1.317" Height="0.4" Width="0.996" />
8 + </Comment>
9 + <Class Name="WixToolset.Dtf.Compression.ArchiveException" Collapsed="true">
10 + <Position X="3" Y="4.25" Width="2" />
11 + <TypeIdentifier>
12 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
13 + <FileName>ArchiveException.cs</FileName>
14 + </TypeIdentifier>
15 + </Class>
16 + <Class Name="WixToolset.Dtf.Compression.ArchiveFileInfo">
17 + <Position X="3" Y="0.5" Width="2" />
18 + <Members>
19 + <Method Name="ArchiveFileInfo" Hidden="true" />
20 + <Field Name="archiveInfo" Hidden="true" />
21 + <Field Name="archiveNumber" Hidden="true" />
22 + <Field Name="attributes" Hidden="true" />
23 + <Field Name="exists" Hidden="true" />
24 + <Method Name="GetObjectData" Hidden="true" />
25 + <Field Name="initialized" Hidden="true" />
26 + <Field Name="lastWriteTime" Hidden="true" />
27 + <Field Name="length" Hidden="true" />
28 + <Field Name="name" Hidden="true" />
29 + <Field Name="path" Hidden="true" />
30 + </Members>
31 + <TypeIdentifier>
32 + <HashCode>AAAgAAAAIRJAAIMEAEACgARwAAEEEAAAASAAAAEAIAA=</HashCode>
33 + <FileName>ArchiveFileInfo.cs</FileName>
34 + </TypeIdentifier>
35 + </Class>
36 + <Class Name="WixToolset.Dtf.Compression.ArchiveInfo">
37 + <Position X="0.5" Y="0.5" Width="2.25" />
38 + <Members>
39 + <Method Name="ArchiveInfo" Hidden="true" />
40 + <Method Name="CreateStringDictionary" Hidden="true" />
41 + <Method Name="GetFile" Hidden="true" />
42 + <Method Name="GetRelativeFilePathsInDirectoryTree" Hidden="true" />
43 + <Method Name="InternalGetFiles" Hidden="true" />
44 + <Method Name="RecursiveGetRelativeFilePathsInDirectoryTree" Hidden="true" />
45 + </Members>
46 + <TypeIdentifier>
47 + <HashCode>AAEAABAAIAAAAgQEAAgBAARAHAEJACAAAABEAAkAMAI=</HashCode>
48 + <FileName>ArchiveInfo.cs</FileName>
49 + </TypeIdentifier>
50 + </Class>
51 + <Class Name="WixToolset.Dtf.Compression.ArchiveFileStreamContext">
52 + <Position X="12.75" Y="0.75" Width="2.25" />
53 + <Members>
54 + <Field Name="archiveFiles" Hidden="true" />
55 + <Method Name="ArchiveFileStreamContext" Hidden="true" />
56 + <Method Name="CloseArchiveReadStream" Hidden="true" />
57 + <Method Name="CloseArchiveWriteStream" Hidden="true" />
58 + <Method Name="CloseFileReadStream" Hidden="true" />
59 + <Method Name="CloseFileWriteStream" Hidden="true" />
60 + <Field Name="directory" Hidden="true" />
61 + <Field Name="enableOffsetOpen" Hidden="true" />
62 + <Field Name="extractOnlyNewerFiles" Hidden="true" />
63 + <Field Name="files" Hidden="true" />
64 + <Method Name="GetArchiveName" Hidden="true" />
65 + <Method Name="GetOption" Hidden="true" />
66 + <Method Name="OpenArchiveReadStream" Hidden="true" />
67 + <Method Name="OpenArchiveWriteStream" Hidden="true" />
68 + <Method Name="OpenFileReadStream" Hidden="true" />
69 + <Method Name="OpenFileWriteStream" Hidden="true" />
70 + <Method Name="TranslateFilePath" Hidden="true" />
71 + </Members>
72 + <TypeIdentifier>
73 + <HashCode>AEQAABgAAACQAACACACAAgAQAAIgAAAAACAMgAAEAKA=</HashCode>
74 + <FileName>ArchiveFileStreamContext.cs</FileName>
75 + </TypeIdentifier>
76 + <Lollipop Position="0.2" />
77 + </Class>
78 + <Class Name="WixToolset.Dtf.Compression.ArchiveProgressEventArgs">
79 + <Position X="5.25" Y="0.5" Width="2.25" />
80 + <Members>
81 + <Method Name="ArchiveProgressEventArgs" Hidden="true" />
82 + <Field Name="currentArchiveBytesProcessed" Hidden="true" />
83 + <Field Name="currentArchiveName" Hidden="true" />
84 + <Field Name="currentArchiveNumber" Hidden="true" />
85 + <Field Name="currentArchiveTotalBytes" Hidden="true" />
86 + <Field Name="currentFileBytesProcessed" Hidden="true" />
87 + <Field Name="currentFileName" Hidden="true" />
88 + <Field Name="currentFileNumber" Hidden="true" />
89 + <Field Name="currentFileTotalBytes" Hidden="true" />
90 + <Field Name="fileBytesProcessed" Hidden="true" />
91 + <Field Name="progressType" Hidden="true" />
92 + <Field Name="totalArchives" Hidden="true" />
93 + <Field Name="totalFileBytes" Hidden="true" />
94 + <Field Name="totalFiles" Hidden="true" />
95 + </Members>
96 + <TypeIdentifier>
97 + <HashCode>AAMCAQASACAAABBBAAASUAAAQBAAAMAAAAGQAAgBEAA=</HashCode>
98 + <FileName>ArchiveProgressEventArgs.cs</FileName>
99 + </TypeIdentifier>
100 + </Class>
101 + <Class Name="WixToolset.Dtf.Compression.BasicUnpackStreamContext">
102 + <Position X="12.75" Y="3" Width="2.25" />
103 + <Members>
104 + <Field Name="archiveStream" Hidden="true" />
105 + <Method Name="BasicUnpackStreamContext" Hidden="true" />
106 + <Method Name="CloseArchiveReadStream" Hidden="true" />
107 + <Method Name="CloseFileWriteStream" Hidden="true" />
108 + <Field Name="fileStream" Hidden="true" />
109 + <Method Name="OpenArchiveReadStream" Hidden="true" />
110 + <Method Name="OpenFileWriteStream" Hidden="true" />
111 + </Members>
112 + <TypeIdentifier>
113 + <HashCode>AAAAAAgAAACEAAAAAAAAAAAAAAAgAAAAIAAMAAAAAAA=</HashCode>
114 + <FileName>BasicUnpackStreamContext.cs</FileName>
115 + </TypeIdentifier>
116 + <Lollipop Position="0.2" />
117 + </Class>
118 + <Class Name="WixToolset.Dtf.Compression.CompressionEngine">
119 + <Position X="8" Y="0.5" Width="2.25" />
120 + <Members>
121 + <Method Name="~CompressionEngine" Hidden="true" />
122 + <Method Name="CompressionEngine" Hidden="true" />
123 + <Field Name="compressionLevel" Hidden="true" />
124 + <Field Name="dontUseTempFiles" Hidden="true" />
125 + </Members>
126 + <TypeIdentifier>
127 + <HashCode>AAAEAAAABCBAACRgAAAAAAQAAEAAAAAAQAEAAAiAAAI=</HashCode>
128 + <FileName>CompressionEngine.cs</FileName>
129 + </TypeIdentifier>
130 + <Lollipop Position="0.2" />
131 + </Class>
132 + <Class Name="WixToolset.Dtf.Compression.DuplicateStream" Collapsed="true">
133 + <Position X="10.5" Y="4.25" Width="2" />
134 + <TypeIdentifier>
135 + <HashCode>AAAAAEAAAgAAQAIgGAAAIABgAAAAAAAAAAAAAAGIACA=</HashCode>
136 + <FileName>DuplicateStream.cs</FileName>
137 + </TypeIdentifier>
138 + </Class>
139 + <Class Name="WixToolset.Dtf.Compression.OffsetStream" Collapsed="true">
140 + <Position X="8" Y="4.25" Width="2" />
141 + <TypeIdentifier>
142 + <HashCode>AAAAAAAAAgAAQAIgGAAAAABgAAAAAEAgAAAAAAGIwCA=</HashCode>
143 + <FileName>OffsetStream.cs</FileName>
144 + </TypeIdentifier>
145 + </Class>
146 + <Interface Name="WixToolset.Dtf.Compression.IPackStreamContext">
147 + <Position X="10.5" Y="0.5" Width="2" />
148 + <TypeIdentifier>
149 + <HashCode>AAAAAAAAAAAAAACAAACAAAAQAAAgAAAAACAIAAAAAAA=</HashCode>
150 + <FileName>IPackStreamContext.cs</FileName>
151 + </TypeIdentifier>
152 + </Interface>
153 + <Interface Name="WixToolset.Dtf.Compression.IUnpackStreamContext">
154 + <Position X="10.5" Y="2.5" Width="2" />
155 + <TypeIdentifier>
156 + <HashCode>AAAAAAgAAACAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAA=</HashCode>
157 + <FileName>IUnpackStreamContext.cs</FileName>
158 + </TypeIdentifier>
159 + </Interface>
160 + <Enum Name="WixToolset.Dtf.Compression.ArchiveProgressType" Collapsed="true">
161 + <Position X="5.25" Y="3.75" Width="2" />
162 + <TypeIdentifier>
163 + <HashCode>QAAAAAAAAAAAAIAAgAAAAAAAAAQAAAAACIAAAAAAAAA=</HashCode>
164 + <FileName>ArchiveProgressType.cs</FileName>
165 + </TypeIdentifier>
166 + </Enum>
167 + <Enum Name="WixToolset.Dtf.Compression.CompressionLevel" Collapsed="true">
168 + <Position X="5.25" Y="4.5" Width="2" />
169 + <TypeIdentifier>
170 + <HashCode>AAAAAAAAABAAAAAAEAAAAAAAAAAIAAAAAAAAAAEAAAA=</HashCode>
171 + <FileName>CompressionLevel.cs</FileName>
172 + </TypeIdentifier>
173 + </Enum>
174 + <Font Name="Verdana" Size="8" />
175 +</ClassDiagram>
\ No newline at end of file
src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs new
+371
@@ -0,0 +1,371 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 +using System;
6 +using System.IO;
7 +using System.Collections.Generic;
8 +using System.Globalization;
9 +
10 + /// <summary>
11 + /// Base class for an engine capable of packing and unpacking a particular
12 + /// compressed file format.
13 + /// </summary>
14 + public abstract class CompressionEngine : IDisposable
15 + {
16 + private CompressionLevel compressionLevel;
17 + private bool dontUseTempFiles;
18 +
19 + /// <summary>
20 + /// Creates a new instance of the compression engine base class.
21 + /// </summary>
22 + protected CompressionEngine()
23 + {
24 + this.compressionLevel = CompressionLevel.Normal;
25 + }
26 +
27 + /// <summary>
28 + /// Disposes the compression engine.
29 + /// </summary>
30 + ~CompressionEngine()
31 + {
32 + this.Dispose(false);
33 + }
34 +
35 + /// <summary>
36 + /// Occurs when the compression engine reports progress in packing
37 + /// or unpacking an archive.
38 + /// </summary>
39 + /// <seealso cref="ArchiveProgressType"/>
40 + public event EventHandler<ArchiveProgressEventArgs> Progress;
41 +
42 + /// <summary>
43 + /// Gets or sets a flag indicating whether temporary files are created
44 + /// and used during compression.
45 + /// </summary>
46 + /// <value>True if temporary files are used; false if compression is done
47 + /// entirely in-memory.</value>
48 + /// <remarks>The value of this property is true by default. Using temporary
49 + /// files can greatly reduce the memory requirement of compression,
50 + /// especially when compressing large archives. However, setting this property
51 + /// to false may yield slightly better performance when creating small
52 + /// archives. Or it may be necessary if the process does not have sufficient
53 + /// privileges to create temporary files.</remarks>
54 + public bool UseTempFiles
55 + {
56 + get
57 + {
58 + return !this.dontUseTempFiles;
59 + }
60 +
61 + set
62 + {
63 + this.dontUseTempFiles = !value;
64 + }
65 + }
66 +
67 + /// <summary>
68 + /// Compression level to use when compressing files.
69 + /// </summary>
70 + /// <value>A compression level ranging from minimum to maximum compression,
71 + /// or no compression.</value>
72 + public CompressionLevel CompressionLevel
73 + {
74 + get
75 + {
76 + return this.compressionLevel;
77 + }
78 +
79 + set
80 + {
81 + this.compressionLevel = value;
82 + }
83 + }
84 +
85 + /// <summary>
86 + /// Disposes of resources allocated by the compression engine.
87 + /// </summary>
88 + public void Dispose()
89 + {
90 + this.Dispose(true);
91 + GC.SuppressFinalize(this);
92 + }
93 +
94 + /// <summary>
95 + /// Creates an archive.
96 + /// </summary>
97 + /// <param name="streamContext">A context interface to handle opening
98 + /// and closing of archive and file streams.</param>
99 + /// <param name="files">The paths of the files in the archive
100 + /// (not external file paths).</param>
101 + /// <exception cref="ArchiveException">The archive could not be
102 + /// created.</exception>
103 + /// <remarks>
104 + /// The stream context implementation may provide a mapping from the
105 + /// file paths within the archive to the external file paths.
106 + /// </remarks>
107 + public void Pack(IPackStreamContext streamContext, IEnumerable<string> files)
108 + {
109 + if (files == null)
110 + {
111 + throw new ArgumentNullException("files");
112 + }
113 +
114 + this.Pack(streamContext, files, 0);
115 + }
116 +
117 + /// <summary>
118 + /// Creates an archive or chain of archives.
119 + /// </summary>
120 + /// <param name="streamContext">A context interface to handle opening
121 + /// and closing of archive and file streams.</param>
122 + /// <param name="files">The paths of the files in the archive (not
123 + /// external file paths).</param>
124 + /// <param name="maxArchiveSize">The maximum number of bytes for one
125 + /// archive before the contents are chained to the next archive, or zero
126 + /// for unlimited archive size.</param>
127 + /// <exception cref="ArchiveException">The archive could not be
128 + /// created.</exception>
129 + /// <remarks>
130 + /// The stream context implementation may provide a mapping from the file
131 + /// paths within the archive to the external file paths.
132 + /// </remarks>
133 + public abstract void Pack(
134 + IPackStreamContext streamContext,
135 + IEnumerable<string> files,
136 + long maxArchiveSize);
137 +
138 + /// <summary>
139 + /// Checks whether a Stream begins with a header that indicates
140 + /// it is a valid archive.
141 + /// </summary>
142 + /// <param name="stream">Stream for reading the archive file.</param>
143 + /// <returns>True if the stream is a valid archive
144 + /// (with no offset); false otherwise.</returns>
145 + public abstract bool IsArchive(Stream stream);
146 +
147 + /// <summary>
148 + /// Gets the offset of an archive that is positioned 0 or more bytes
149 + /// from the start of the Stream.
150 + /// </summary>
151 + /// <param name="stream">A stream for reading the archive.</param>
152 + /// <returns>The offset in bytes of the archive,
153 + /// or -1 if no archive is found in the Stream.</returns>
154 + /// <remarks>The archive must begin on a 4-byte boundary.</remarks>
155 + public virtual long FindArchiveOffset(Stream stream)
156 + {
157 + if (stream == null)
158 + {
159 + throw new ArgumentNullException("stream");
160 + }
161 +
162 + long sectionSize = 4;
163 + long length = stream.Length;
164 + for (long offset = 0; offset <= length - sectionSize; offset += sectionSize)
165 + {
166 + stream.Seek(offset, SeekOrigin.Begin);
167 + if (this.IsArchive(stream))
168 + {
169 + return offset;
170 + }
171 + }
172 +
173 + return -1;
174 + }
175 +
176 + /// <summary>
177 + /// Gets information about all files in an archive stream.
178 + /// </summary>
179 + /// <param name="stream">A stream for reading the archive.</param>
180 + /// <returns>Information about all files in the archive stream.</returns>
181 + /// <exception cref="ArchiveException">The stream is not a valid
182 + /// archive.</exception>
183 + public IList<ArchiveFileInfo> GetFileInfo(Stream stream)
184 + {
185 + return this.GetFileInfo(new BasicUnpackStreamContext(stream), null);
186 + }
187 +
188 + /// <summary>
189 + /// Gets information about files in an archive or archive chain.
190 + /// </summary>
191 + /// <param name="streamContext">A context interface to handle opening
192 + /// and closing of archive and file streams.</param>
193 + /// <param name="fileFilter">A predicate that can determine
194 + /// which files to process, optional.</param>
195 + /// <returns>Information about files in the archive stream.</returns>
196 + /// <exception cref="ArchiveException">The archive provided
197 + /// by the stream context is not valid.</exception>
198 + /// <remarks>
199 + /// The <paramref name="fileFilter"/> predicate takes an internal file
200 + /// path and returns true to include the file or false to exclude it.
201 + /// </remarks>
202 + public abstract IList<ArchiveFileInfo> GetFileInfo(
203 + IUnpackStreamContext streamContext,
204 + Predicate<string> fileFilter);
205 +
206 + /// <summary>
207 + /// Gets the list of files in an archive Stream.
208 + /// </summary>
209 + /// <param name="stream">A stream for reading the archive.</param>
210 + /// <returns>A list of the paths of all files contained in the
211 + /// archive.</returns>
212 + /// <exception cref="ArchiveException">The stream is not a valid
213 + /// archive.</exception>
214 + public IList<string> GetFiles(Stream stream)
215 + {
216 + return this.GetFiles(new BasicUnpackStreamContext(stream), null);
217 + }
218 +
219 + /// <summary>
220 + /// Gets the list of files in an archive or archive chain.
221 + /// </summary>
222 + /// <param name="streamContext">A context interface to handle opening
223 + /// and closing of archive and file streams.</param>
224 + /// <param name="fileFilter">A predicate that can determine
225 + /// which files to process, optional.</param>
226 + /// <returns>An array containing the names of all files contained in
227 + /// the archive or archive chain.</returns>
228 + /// <exception cref="ArchiveException">The archive provided
229 + /// by the stream context is not valid.</exception>
230 + /// <remarks>
231 + /// The <paramref name="fileFilter"/> predicate takes an internal file
232 + /// path and returns true to include the file or false to exclude it.
233 + /// </remarks>
234 + public IList<string> GetFiles(
235 + IUnpackStreamContext streamContext,
236 + Predicate<string> fileFilter)
237 + {
238 + if (streamContext == null)
239 + {
240 + throw new ArgumentNullException("streamContext");
241 + }
242 +
243 + IList<ArchiveFileInfo> files =
244 + this.GetFileInfo(streamContext, fileFilter);
245 + IList<string> fileNames = new List<string>(files.Count);
246 + for (int i = 0; i < files.Count; i++)
247 + {
248 + fileNames.Add(files[i].Name);
249 + }
250 +
251 + return fileNames;
252 + }
253 +
254 + /// <summary>
255 + /// Reads a single file from an archive stream.
256 + /// </summary>
257 + /// <param name="stream">A stream for reading the archive.</param>
258 + /// <param name="path">The path of the file within the archive
259 + /// (not the external file path).</param>
260 + /// <returns>A stream for reading the extracted file, or null
261 + /// if the file does not exist in the archive.</returns>
262 + /// <exception cref="ArchiveException">The stream is not a valid
263 + /// archive.</exception>
264 + /// <remarks>The entire extracted file is cached in memory, so this
265 + /// method requires enough free memory to hold the file.</remarks>
266 + public Stream Unpack(Stream stream, string path)
267 + {
268 + if (stream == null)
269 + {
270 + throw new ArgumentNullException("stream");
271 + }
272 +
273 + if (path == null)
274 + {
275 + throw new ArgumentNullException("path");
276 + }
277 +
278 + BasicUnpackStreamContext streamContext =
279 + new BasicUnpackStreamContext(stream);
280 + this.Unpack(
281 + streamContext,
282 + delegate(string match)
283 + {
284 + return String.Compare(
285 + match, path, true, CultureInfo.InvariantCulture) == 0;
286 + });
287 +
288 + Stream extractStream = streamContext.FileStream;
289 + if (extractStream != null)
290 + {
291 + extractStream.Position = 0;
292 + }
293 +
294 + return extractStream;
295 + }
296 +
297 + /// <summary>
298 + /// Extracts files from an archive or archive chain.
299 + /// </summary>
300 + /// <param name="streamContext">A context interface to handle opening
301 + /// and closing of archive and file streams.</param>
302 + /// <param name="fileFilter">An optional predicate that can determine
303 + /// which files to process.</param>
304 + /// <exception cref="ArchiveException">The archive provided
305 + /// by the stream context is not valid.</exception>
306 + /// <remarks>
307 + /// The <paramref name="fileFilter"/> predicate takes an internal file
308 + /// path and returns true to include the file or false to exclude it.
309 + /// </remarks>
310 + public abstract void Unpack(
311 + IUnpackStreamContext streamContext,
312 + Predicate<string> fileFilter);
313 +
314 + /// <summary>
315 + /// Called by sublcasses to distribute a packing or unpacking progress
316 + /// event to listeners.
317 + /// </summary>
318 + /// <param name="e">Event details.</param>
319 + protected void OnProgress(ArchiveProgressEventArgs e)
320 + {
321 + if (this.Progress != null)
322 + {
323 + this.Progress(this, e);
324 + }
325 + }
326 +
327 + /// <summary>
328 + /// Disposes of resources allocated by the compression engine.
329 + /// </summary>
330 + /// <param name="disposing">If true, the method has been called
331 + /// directly or indirectly by a user's code, so managed and unmanaged
332 + /// resources will be disposed. If false, the method has been called by
333 + /// the runtime from inside the finalizer, and only unmanaged resources
334 + /// will be disposed.</param>
335 + protected virtual void Dispose(bool disposing)
336 + {
337 + }
338 +
339 + /// <summary>
340 + /// Compresion utility function for converting old-style
341 + /// date and time values to a DateTime structure.
342 + /// </summary>
343 + public static void DosDateAndTimeToDateTime(
344 + short dosDate, short dosTime, out DateTime dateTime)
345 + {
346 + if (dosDate == 0 && dosTime == 0)
347 + {
348 + dateTime = DateTime.MinValue;
349 + }
350 + else
351 + {
352 + long fileTime;
353 + SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime);
354 + dateTime = DateTime.FromFileTimeUtc(fileTime);
355 + dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local);
356 + }
357 + }
358 +
359 + /// <summary>
360 + /// Compresion utility function for converting a DateTime structure
361 + /// to old-style date and time values.
362 + /// </summary>
363 + public static void DateTimeToDosDateAndTime(
364 + DateTime dateTime, out short dosDate, out short dosTime)
365 + {
366 + dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
367 + long filetime = dateTime.ToFileTimeUtc();
368 + SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime);
369 + }
370 + }
371 +}
src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs new
+31
@@ -0,0 +1,31 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Dtf.Compression
4 +{
5 +using System;
6 +using System.Collections.Generic;
7 +using System.Text;
8 +
9 + /// <summary>
10 + /// Specifies the compression level ranging from minimum compresion to
11 + /// maximum compression, or no compression at all.
12 + /// </summary>
13 + /// <remarks>
14 + /// Although only four values are enumerated, any integral value between
15 + /// <see cref="CompressionLevel.Min"/> and <see cref="CompressionLevel.Max"/> can also be used.
16 + /// </remarks>
17 + public enum CompressionLevel
18 + {
19 + /// <summary>Do not compress files, only store.</summary>
20 + None = 0,
21 +
22 + /// <summary>Minimum compression; fastest.</summary>
23 + Min = 1,
24 +
25 + /// <summary>A compromize between speed and compression efficiency.</summary>
26 + Normal = 6,
27 +
28 + /// <summary>Maximum compression; slowest.</summary>
29 + Max = 10
30 + }
31 +}
src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs new
+212
@@ -0,0 +1,212 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + /// <summary>
9 + /// Duplicates a source stream by maintaining a separate position.
10 + /// </summary>
11 + /// <remarks>
12 + /// WARNING: duplicate streams are not thread-safe with respect to each other or the original stream.
13 + /// If multiple threads use duplicate copies of the same stream, they must synchronize for any operations.
14 + /// </remarks>
15 + public class DuplicateStream : Stream
16 + {
17 + private Stream source;
18 + private long position;
19 +
20 + /// <summary>
21 + /// Creates a new duplicate of a stream.
22 + /// </summary>
23 + /// <param name="source">source of the duplicate</param>
24 + public DuplicateStream(Stream source)
25 + {
26 + if (source == null)
27 + {
28 + throw new ArgumentNullException("source");
29 + }
30 +
31 + this.source = DuplicateStream.OriginalStream(source);
32 + }
33 +
34 + /// <summary>
35 + /// Gets the original stream that was used to create the duplicate.
36 + /// </summary>
37 + public Stream Source
38 + {
39 + get
40 + {
41 + return this.source;
42 + }
43 + }
44 +
45 + /// <summary>
46 + /// Gets a value indicating whether the source stream supports reading.
47 + /// </summary>
48 + /// <value>true if the stream supports reading; otherwise, false.</value>
49 + public override bool CanRead
50 + {
51 + get
52 + {
53 + return this.source.CanRead;
54 + }
55 + }
56 +
57 + /// <summary>
58 + /// Gets a value indicating whether the source stream supports writing.
59 + /// </summary>
60 + /// <value>true if the stream supports writing; otherwise, false.</value>
61 + public override bool CanWrite
62 + {
63 + get
64 + {
65 + return this.source.CanWrite;
66 + }
67 + }
68 +
69 + /// <summary>
70 + /// Gets a value indicating whether the source stream supports seeking.
71 + /// </summary>
72 + /// <value>true if the stream supports seeking; otherwise, false.</value>
73 + public override bool CanSeek
74 + {
75 + get
76 + {
77 + return this.source.CanSeek;
78 + }
79 + }
80 +
81 + /// <summary>
82 + /// Gets the length of the source stream.
83 + /// </summary>
84 + public override long Length
85 + {
86 + get
87 + {
88 + return this.source.Length;
89 + }
90 + }
91 +
92 + /// <summary>
93 + /// Gets or sets the position of the current stream,
94 + /// ignoring the position of the source stream.
95 + /// </summary>
96 + public override long Position
97 + {
98 + get
99 + {
100 + return this.position;
101 + }
102 +
103 + set
104 + {
105 + this.position = value;
106 + }
107 + }
108 +
109 + /// <summary>
110 + /// Retrieves the original stream from a possible duplicate stream.
111 + /// </summary>
112 + /// <param name="stream">Possible duplicate stream.</param>
113 + /// <returns>If the stream is a DuplicateStream, returns
114 + /// the duplicate's source; otherwise returns the same stream.</returns>
115 + public static Stream OriginalStream(Stream stream)
116 + {
117 + DuplicateStream dupStream = stream as DuplicateStream;
118 + return dupStream != null ? dupStream.Source : stream;
119 + }
120 +
121 + /// <summary>
122 + /// Flushes the source stream.
123 + /// </summary>
124 + public override void Flush()
125 + {
126 + this.source.Flush();
127 + }
128 +
129 + /// <summary>
130 + /// Sets the length of the source stream.
131 + /// </summary>
132 + /// <param name="value">The desired length of the stream in bytes.</param>
133 + public override void SetLength(long value)
134 + {
135 + this.source.SetLength(value);
136 + }
137 +
138 + /// <summary>
139 + /// Closes the underlying stream, effectively closing ALL duplicates.
140 + /// </summary>
141 + public override void Close()
142 + {
143 + this.source.Close();
144 + }
145 +
146 + /// <summary>
147 + /// Reads from the source stream while maintaining a separate position
148 + /// and not impacting the source stream's position.
149 + /// </summary>
150 + /// <param name="buffer">An array of bytes. When this method returns, the buffer
151 + /// contains the specified byte array with the values between offset and
152 + /// (offset + count - 1) replaced by the bytes read from the current source.</param>
153 + /// <param name="offset">The zero-based byte offset in buffer at which to begin
154 + /// storing the data read from the current stream.</param>
155 + /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
156 + /// <returns>The total number of bytes read into the buffer. This can be less
157 + /// than the number of bytes requested if that many bytes are not currently available,
158 + /// or zero (0) if the end of the stream has been reached.</returns>
159 + public override int Read(byte[] buffer, int offset, int count)
160 + {
161 + long saveSourcePosition = this.source.Position;
162 + this.source.Position = this.position;
163 + int read = this.source.Read(buffer, offset, count);
164 + this.position = this.source.Position;
165 + this.source.Position = saveSourcePosition;
166 + return read;
167 + }
168 +
169 + /// <summary>
170 + /// Writes to the source stream while maintaining a separate position
171 + /// and not impacting the source stream's position.
172 + /// </summary>
173 + /// <param name="buffer">An array of bytes. This method copies count
174 + /// bytes from buffer to the current stream.</param>
175 + /// <param name="offset">The zero-based byte offset in buffer at which
176 + /// to begin copying bytes to the current stream.</param>
177 + /// <param name="count">The number of bytes to be written to the
178 + /// current stream.</param>
179 + public override void Write(byte[] buffer, int offset, int count)
180 + {
181 + long saveSourcePosition = this.source.Position;
182 + this.source.Position = this.position;
183 + this.source.Write(buffer, offset, count);
184 + this.position = this.source.Position;
185 + this.source.Position = saveSourcePosition;
186 + }
187 +
188 + /// <summary>
189 + /// Changes the position of this stream without impacting the
190 + /// source stream's position.
191 + /// </summary>
192 + /// <param name="offset">A byte offset relative to the origin parameter.</param>
193 + /// <param name="origin">A value of type SeekOrigin indicating the reference
194 + /// point used to obtain the new position.</param>
195 + /// <returns>The new position within the current stream.</returns>
196 + public override long Seek(long offset, SeekOrigin origin)
197 + {
198 + long originPosition = 0;
199 + if (origin == SeekOrigin.Current)
200 + {
201 + originPosition = this.position;
202 + }
203 + else if (origin == SeekOrigin.End)
204 + {
205 + originPosition = this.Length;
206 + }
207 +
208 + this.position = originPosition + offset;
209 + return this.position;
210 + }
211 + }
212 +}
src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs new
+117
@@ -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 +
3 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Diagnostics.CodeAnalysis;
8 +
9 + /// <summary>
10 + /// This interface provides the methods necessary for the
11 + /// <see cref="CompressionEngine"/> to open and close streams for archives
12 + /// and files. The implementor of this interface can use any kind of logic
13 + /// to determine what kind of streams to open and where.
14 + /// </summary>
15 + public interface IPackStreamContext
16 + {
17 + /// <summary>
18 + /// Gets the name of the archive with a specified number.
19 + /// </summary>
20 + /// <param name="archiveNumber">The 0-based index of the archive
21 + /// within the chain.</param>
22 + /// <returns>The name of the requested archive. May be an empty string
23 + /// for non-chained archives, but may never be null.</returns>
24 + /// <remarks>The archive name is the name stored within the archive, used for
25 + /// identification of the archive especially among archive chains. That
26 + /// name is often, but not necessarily the same as the filename of the
27 + /// archive package.</remarks>
28 + string GetArchiveName(int archiveNumber);
29 +
30 + /// <summary>
31 + /// Opens a stream for writing an archive package.
32 + /// </summary>
33 + /// <param name="archiveNumber">The 0-based index of the archive within
34 + /// the chain.</param>
35 + /// <param name="archiveName">The name of the archive that was returned
36 + /// by <see cref="GetArchiveName"/>.</param>
37 + /// <param name="truncate">True if the stream should be truncated when
38 + /// opened (if it already exists); false if an existing stream is being
39 + /// re-opened for writing additional data.</param>
40 + /// <param name="compressionEngine">Instance of the compression engine
41 + /// doing the operations.</param>
42 + /// <returns>A writable Stream where the compressed archive bytes will be
43 + /// written, or null to cancel the archive creation.</returns>
44 + /// <remarks>
45 + /// If this method returns null, the archive engine will throw a
46 + /// FileNotFoundException.
47 + /// </remarks>
48 + Stream OpenArchiveWriteStream(
49 + int archiveNumber,
50 + string archiveName,
51 + bool truncate,
52 + CompressionEngine compressionEngine);
53 +
54 + /// <summary>
55 + /// Closes a stream where an archive package was written.
56 + /// </summary>
57 + /// <param name="archiveNumber">The 0-based index of the archive within
58 + /// the chain.</param>
59 + /// <param name="archiveName">The name of the archive that was previously
60 + /// returned by
61 + /// <see cref="GetArchiveName"/>.</param>
62 + /// <param name="stream">A stream that was previously returned by
63 + /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
64 + /// <remarks>
65 + /// If there is another archive package in the chain, then after this stream
66 + /// is closed a new stream will be opened.
67 + /// </remarks>
68 + void CloseArchiveWriteStream(int archiveNumber, string archiveName, Stream stream);
69 +
70 + /// <summary>
71 + /// Opens a stream to read a file that is to be included in an archive.
72 + /// </summary>
73 + /// <param name="path">The path of the file within the archive. This is often,
74 + /// but not necessarily, the same as the relative path of the file outside
75 + /// the archive.</param>
76 + /// <param name="attributes">Returned attributes of the opened file, to be
77 + /// stored in the archive.</param>
78 + /// <param name="lastWriteTime">Returned last-modified time of the opened file,
79 + /// to be stored in the archive.</param>
80 + /// <returns>A readable Stream where the file bytes will be read from before
81 + /// they are compressed, or null to skip inclusion of the file and continue to
82 + /// the next file.</returns>
83 + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
84 + Stream OpenFileReadStream(
85 + string path,
86 + out FileAttributes attributes,
87 + out DateTime lastWriteTime);
88 +
89 + /// <summary>
90 + /// Closes a stream that has been used to read a file.
91 + /// </summary>
92 + /// <param name="path">The path of the file within the archive; the same as
93 + /// the path provided
94 + /// when the stream was opened.</param>
95 + /// <param name="stream">A stream that was previously returned by
96 + /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
97 + void CloseFileReadStream(string path, Stream stream);
98 +
99 + /// <summary>
100 + /// Gets extended parameter information specific to the compression
101 + /// format being used.
102 + /// </summary>
103 + /// <param name="optionName">Name of the option being requested.</param>
104 + /// <param name="parameters">Parameters for the option; for per-file options,
105 + /// the first parameter is typically the internal file path.</param>
106 + /// <returns>Option value, or null to use the default behavior.</returns>
107 + /// <remarks>
108 + /// This method provides a way to set uncommon options during packaging, or a
109 + /// way to handle aspects of compression formats not supported by the base library.
110 + /// <para>For example, this may be used by the zip compression library to
111 + /// specify different compression methods/levels on a per-file basis.</para>
112 + /// <para>The available option names, parameters, and expected return values
113 + /// should be documented by each compression library.</para>
114 + /// </remarks>
115 + object GetOption(string optionName, object[] parameters);
116 + }
117 +}
src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs new
+71
@@ -0,0 +1,71 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + /// <summary>
9 + /// This interface provides the methods necessary for the <see cref="CompressionEngine"/> to open
10 + /// and close streams for archives and files. The implementor of this interface can use any
11 + /// kind of logic to determine what kind of streams to open and where
12 + /// </summary>
13 + public interface IUnpackStreamContext
14 + {
15 + /// <summary>
16 + /// Opens the archive stream for reading.
17 + /// </summary>
18 + /// <param name="archiveNumber">The zero-based index of the archive to open.</param>
19 + /// <param name="archiveName">The name of the archive being opened.</param>
20 + /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
21 + /// <returns>A stream from which archive bytes are read, or null to cancel extraction
22 + /// of the archive.</returns>
23 + /// <remarks>
24 + /// When the first archive in a chain is opened, the name is not yet known, so the
25 + /// provided value will be an empty string. When opening further archives, the
26 + /// provided value is the next-archive name stored in the previous archive. This
27 + /// name is often, but not necessarily, the same as the filename of the archive
28 + /// package to be opened.
29 + /// <para>If this method returns null, the archive engine will throw a
30 + /// FileNotFoundException.</para>
31 + /// </remarks>
32 + Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine);
33 +
34 + /// <summary>
35 + /// Closes a stream where an archive package was read.
36 + /// </summary>
37 + /// <param name="archiveNumber">The archive number of the stream to close.</param>
38 + /// <param name="archiveName">The name of the archive being closed.</param>
39 + /// <param name="stream">The stream that was previously returned by
40 + /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
41 + void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream);
42 +
43 + /// <summary>
44 + /// Opens a stream for writing extracted file bytes.
45 + /// </summary>
46 + /// <param name="path">The path of the file within the archive. This is often, but
47 + /// not necessarily, the same as the relative path of the file outside the archive.</param>
48 + /// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
49 + /// <param name="lastWriteTime">The last write time of the file to be extracted.</param>
50 + /// <returns>A stream where extracted file bytes are to be written, or null to skip
51 + /// extraction of the file and continue to the next file.</returns>
52 + /// <remarks>
53 + /// The implementor may use the path, size and date information to dynamically
54 + /// decide whether or not the file should be extracted.
55 + /// </remarks>
56 + Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime);
57 +
58 + /// <summary>
59 + /// Closes a stream where an extracted file was written.
60 + /// </summary>
61 + /// <param name="path">The path of the file within the archive.</param>
62 + /// <param name="stream">The stream that was previously returned by <see cref="OpenFileWriteStream"/>
63 + /// and is now ready to be closed.</param>
64 + /// <param name="attributes">The attributes of the extracted file.</param>
65 + /// <param name="lastWriteTime">The last write time of the file.</param>
66 + /// <remarks>
67 + /// The implementor may wish to apply the attributes and date to the newly-extracted file.
68 + /// </remarks>
69 + void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime);
70 + }
71 +}
src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs new
+206
@@ -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 +
3 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + /// <summary>
9 + /// Wraps a source stream and offsets all read/write/seek calls by a given value.
10 + /// </summary>
11 + /// <remarks>
12 + /// This class is used to trick archive an packing or unpacking process
13 + /// into reading or writing at an offset into a file, primarily for
14 + /// self-extracting packages.
15 + /// </remarks>
16 + public class OffsetStream : Stream
17 + {
18 + private Stream source;
19 + private long sourceOffset;
20 +
21 + /// <summary>
22 + /// Creates a new OffsetStream instance from a source stream
23 + /// and using a specified offset.
24 + /// </summary>
25 + /// <param name="source">Underlying stream for which all calls will be offset.</param>
26 + /// <param name="offset">Positive or negative number of bytes to offset.</param>
27 + public OffsetStream(Stream source, long offset)
28 + {
29 + if (source == null)
30 + {
31 + throw new ArgumentNullException("source");
32 + }
33 +
34 + this.source = source;
35 + this.sourceOffset = offset;
36 +
37 + this.source.Seek(this.sourceOffset, SeekOrigin.Current);
38 + }
39 +
40 + /// <summary>
41 + /// Gets the underlying stream that this OffsetStream calls into.
42 + /// </summary>
43 + public Stream Source
44 + {
45 + get { return this.source; }
46 + }
47 +
48 + /// <summary>
49 + /// Gets the number of bytes to offset all calls before
50 + /// redirecting to the underlying stream.
51 + /// </summary>
52 + public long Offset
53 + {
54 + get { return this.sourceOffset; }
55 + }
56 +
57 + /// <summary>
58 + /// Gets a value indicating whether the source stream supports reading.
59 + /// </summary>
60 + /// <value>true if the stream supports reading; otherwise, false.</value>
61 + public override bool CanRead
62 + {
63 + get
64 + {
65 + return this.source.CanRead;
66 + }
67 + }
68 +
69 + /// <summary>
70 + /// Gets a value indicating whether the source stream supports writing.
71 + /// </summary>
72 + /// <value>true if the stream supports writing; otherwise, false.</value>
73 + public override bool CanWrite
74 + {
75 + get
76 + {
77 + return this.source.CanWrite;
78 + }
79 + }
80 +
81 + /// <summary>
82 + /// Gets a value indicating whether the source stream supports seeking.
83 + /// </summary>
84 + /// <value>true if the stream supports seeking; otherwise, false.</value>
85 + public override bool CanSeek
86 + {
87 + get
88 + {
89 + return this.source.CanSeek;
90 + }
91 + }
92 +
93 + /// <summary>
94 + /// Gets the effective length of the stream, which is equal to
95 + /// the length of the source stream minus the offset.
96 + /// </summary>
97 + public override long Length
98 + {
99 + get { return this.source.Length - this.sourceOffset; }
100 + }
101 +
102 + /// <summary>
103 + /// Gets or sets the effective position of the stream, which
104 + /// is equal to the position of the source stream minus the offset.
105 + /// </summary>
106 + public override long Position
107 + {
108 + get { return this.source.Position - this.sourceOffset; }
109 + set { this.source.Position = value + this.sourceOffset; }
110 + }
111 +
112 + /// <summary>
113 + /// Reads a sequence of bytes from the source stream and advances
114 + /// the position within the stream by the number of bytes read.
115 + /// </summary>
116 + /// <param name="buffer">An array of bytes. When this method returns, the buffer
117 + /// contains the specified byte array with the values between offset and
118 + /// (offset + count - 1) replaced by the bytes read from the current source.</param>
119 + /// <param name="offset">The zero-based byte offset in buffer at which to begin
120 + /// storing the data read from the current stream.</param>
121 + /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
122 + /// <returns>The total number of bytes read into the buffer. This can be less
123 + /// than the number of bytes requested if that many bytes are not currently available,
124 + /// or zero (0) if the end of the stream has been reached.</returns>
125 + public override int Read(byte[] buffer, int offset, int count)
126 + {
127 + return this.source.Read(buffer, offset, count);
128 + }
129 +
130 + /// <summary>
131 + /// Writes a sequence of bytes to the source stream and advances the
132 + /// current position within this stream by the number of bytes written.
133 + /// </summary>
134 + /// <param name="buffer">An array of bytes. This method copies count
135 + /// bytes from buffer to the current stream.</param>
136 + /// <param name="offset">The zero-based byte offset in buffer at which
137 + /// to begin copying bytes to the current stream.</param>
138 + /// <param name="count">The number of bytes to be written to the
139 + /// current stream.</param>
140 + public override void Write(byte[] buffer, int offset, int count)
141 + {
142 + this.source.Write(buffer, offset, count);
143 + }
144 +
145 + /// <summary>
146 + /// Reads a byte from the stream and advances the position within the
147 + /// source stream by one byte, or returns -1 if at the end of the stream.
148 + /// </summary>
149 + /// <returns>The unsigned byte cast to an Int32, or -1 if at the
150 + /// end of the stream.</returns>
151 + public override int ReadByte()
152 + {
153 + return this.source.ReadByte();
154 + }
155 +
156 + /// <summary>
157 + /// Writes a byte to the current position in the source stream and
158 + /// advances the position within the stream by one byte.
159 + /// </summary>
160 + /// <param name="value">The byte to write to the stream.</param>
161 + public override void WriteByte(byte value)
162 + {
163 + this.source.WriteByte(value);
164 + }
165 +
166 + /// <summary>
167 + /// Flushes the source stream.
168 + /// </summary>
169 + public override void Flush()
170 + {
171 + this.source.Flush();
172 + }
173 +
174 + /// <summary>
175 + /// Sets the position within the current stream, which is
176 + /// equal to the position within the source stream minus the offset.
177 + /// </summary>
178 + /// <param name="offset">A byte offset relative to the origin parameter.</param>
179 + /// <param name="origin">A value of type SeekOrigin indicating
180 + /// the reference point used to obtain the new position.</param>
181 + /// <returns>The new position within the current stream.</returns>
182 + public override long Seek(long offset, SeekOrigin origin)
183 + {
184 + return this.source.Seek(offset + (origin == SeekOrigin.Begin ? this.sourceOffset : 0), origin) - this.sourceOffset;
185 + }
186 +
187 + /// <summary>
188 + /// Sets the effective length of the stream, which is equal to
189 + /// the length of the source stream minus the offset.
190 + /// </summary>
191 + /// <param name="value">The desired length of the
192 + /// current stream in bytes.</param>
193 + public override void SetLength(long value)
194 + {
195 + this.source.SetLength(value + this.sourceOffset);
196 + }
197 +
198 + /// <summary>
199 + /// Closes the underlying stream.
200 + /// </summary>
201 + public override void Close()
202 + {
203 + this.source.Close();
204 + }
205 + }
206 +}
src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs new
+22
@@ -0,0 +1,22 @@
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 +namespace WixToolset.Dtf.Compression
4 +{
5 + using System;
6 + using System.Security;
7 + using System.Runtime.InteropServices;
8 +
9 + [SuppressUnmanagedCodeSecurity]
10 + internal static class SafeNativeMethods
11 + {
12 + [DllImport("kernel32.dll", SetLastError = true)]
13 + [return: MarshalAs(UnmanagedType.Bool)]
14 + internal static extern bool DosDateTimeToFileTime(
15 + short wFatDate, short wFatTime, out long fileTime);
16 +
17 + [DllImport("kernel32.dll", SetLastError = true)]
18 + [return: MarshalAs(UnmanagedType.Bool)]
19 + internal static extern bool FileTimeToDosDateTime(
20 + ref long fileTime, out short wFatDate, out short wFatTime);
21 + }
22 +}
src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj new
+21
@@ -0,0 +1,21 @@
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 + <RootNamespace>WixToolset.Dtf.Compression</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.Compression</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Abstract base libraries for archive packing and unpacking</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <None Include="Compression.cd" />
15 + </ItemGroup>
16 +
17 + <ItemGroup>
18 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 + </ItemGroup>
21 +</Project>
src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj new
+44
@@ -0,0 +1,44 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +<Project Sdk="Microsoft.NET.Sdk">
5 +
6 + <PropertyGroup>
7 + <TargetFramework>netcoreapp3.1</TargetFramework>
8 + <IncludeBuildOutput>false</IncludeBuildOutput>
9 + <Description>WiX Toolset Dtf MSBuild integration</Description>
10 + <NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile>
11 + <NuspecBasePath>$(OutputPath)publish\WixToolset.Dtf.MSBuild\</NuspecBasePath>
12 + <NuspecProperties>Id=$(MSBuildThisFileName);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description)</NuspecProperties>
13 + </PropertyGroup>
14 +
15 + <ItemGroup>
16 + <None Remove="build\WixToolset.Dtf.MSBuild.props" />
17 + <None Remove="tools\wix.ca.targets" />
18 + </ItemGroup>
19 +
20 + <ItemGroup>
21 + <Content Include="build\WixToolset.Dtf.MSBuild.props">
22 + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23 + </Content>
24 + <Content Include="tools\wix.ca.targets">
25 + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26 + </Content>
27 + </ItemGroup>
28 +
29 + <ItemGroup>
30 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
31 + </ItemGroup>
32 +
33 + <PropertyGroup>
34 + <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuspecVersion</GenerateNuspecDependsOn>
35 + </PropertyGroup>
36 +
37 + <Target Name="SetNuspecVersion">
38 + <Error Text="Cannot pack $(MSBuildThisFileName) until all projects are published to: '$(NuspecBasePath)'. Run appveyor.cmd to publish projects properly." Condition=" !Exists('$(NuspecBasePath)') " />
39 +
40 + <PropertyGroup>
41 + <NuspecProperties>$(NuspecProperties);Version=$(Version);ProjectFolder=$(MSBuildThisFileDirectory)</NuspecProperties>
42 + </PropertyGroup>
43 + </Target>
44 +</Project>
src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec new
+18
@@ -0,0 +1,18 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 + <metadata>
4 + <id>$id$</id>
5 + <version>$version$</version>
6 + <authors>$authors$</authors>
7 + <owners>$authors$</owners>
8 + <license type="expression">MS-RL</license>
9 + <requireLicenseAcceptance>false</requireLicenseAcceptance>
10 + <description>$description$</description>
11 + <copyright>$copyright$</copyright>
12 + </metadata>
13 +
14 + <files>
15 + <file src="build\**\*" target="build" />
16 + <file src="tools\**\*" target="tools" />
17 + </files>
18 +</package>
src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 + <PropertyGroup>
6 + <WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' ">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\tools\wix.ca.targets'))</WixCATargetsPath>
7 + </PropertyGroup>
8 +</Project>
src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets new
+123
@@ -0,0 +1,123 @@
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 +
5 +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 +
7 + <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" />
8 +
9 + <PropertyGroup>
10 + <WixCATargetsImported>true</WixCATargetsImported>
11 +
12 + <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName>
13 +
14 + <WixSdkPath Condition=" '$(WixSdkPath)' == '' ">$(MSBuildThisFileDirectory)</WixSdkPath>
15 + <WixSdkX86Path Condition=" '$(WixSdkX86Path)' == '' ">$(WixSdkPath)x86\</WixSdkX86Path>
16 + <WixSdkX64Path Condition=" '$(WixSdkX64Path)' == '' ">$(WixSdkPath)x64\</WixSdkX64Path>
17 +
18 + <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(WixSdkPath)MakeSfxCA.exe</MakeSfxCA>
19 + <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(WixSdkX64Path)SfxCA.dll</SfxCADll>
20 + <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(WixSdkX86Path)SfxCA.dll</SfxCADll>
21 + </PropertyGroup>
22 +
23 + <!--
24 + ==================================================================================================
25 + PackCustomAction
26 +
27 + Creates an MSI managed custom action package that includes the custom action assembly,
28 + local assembly dependencies, and project content files.
29 +
30 + [IN]
31 + @(IntermediateAssembly) - Managed custom action assembly.
32 + @(Content) - Project items of type Content will be included in the package.
33 + $(CustomActionContents) - Optional space-delimited list of additional files to include.
34 +
35 + [OUT]
36 + $(IntermediateOutputPath)$(TargetCAFileName) - Managed custom action package with unmanaged stub.
37 + ==================================================================================================
38 + -->
39 + <Target Name="PackCustomAction"
40 + Inputs="@(IntermediateAssembly);@(Content);$(CustomActionContents)"
41 + Outputs="$(IntermediateOutputPath)$(TargetCAFileName)">
42 +
43 + <!-- Find all referenced items marked CopyLocal, but exclude non-binary files. -->
44 + <ItemGroup>
45 + <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
46 + Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
47 + <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
48 + Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
49 +
50 + <!-- include PDBs for Debug only -->
51 + <CustomActionReferenceContents Include="@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).pdb')"
52 + Condition=" Exists('%(RootDir)%(Directory)%(Filename).pdb') and '$(Configuration)' == 'Debug' " />
53 + <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
54 + Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
55 + <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
56 + Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
57 + </ItemGroup>
58 +
59 + <!--
60 + Items to include in the CA package:
61 + - Reference assemblies marked CopyLocal
62 + - Project items of type Content
63 + - Additional items in the CustomActionContents property
64 + -->
65 + <PropertyGroup>
66 + <CustomActionContents>@(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents)</CustomActionContents>
67 + </PropertyGroup>
68 +
69 + <ItemGroup>
70 + <IntermediateCAAssembly Include="@(IntermediateAssembly->'%(FullPath)')" />
71 + <IntermediateCAPackage Include="@(IntermediateAssembly->'%(RootDir)%(Directory)$(TargetCAFileName)')" />
72 + </ItemGroup>
73 +
74 + <!-- Run the MakeSfxCA.exe CA packaging tool. -->
75 + <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"'
76 + WorkingDirectory="$(ProjectDir)" />
77 +
78 + <!-- Add modules to be copied to output dir. -->
79 + <ItemGroup>
80 + <AddModules Include="@(IntermediateCAPackage)" />
81 + </ItemGroup>
82 + </Target>
83 +
84 + <!--
85 + ==================================================================================================
86 + CleanCustomAction
87 +
88 + Cleans the .CA.dll binary created by the PackCustomAction target.
89 +
90 + ==================================================================================================
91 + -->
92 + <Target Name="CleanCustomAction">
93 + <Delete Files="$(IntermediateOutputPath)$(TargetCAFileName)"
94 + TreatErrorsAsWarnings="true" />
95 + </Target>
96 +
97 + <!--
98 + ==================================================================================================
99 + AfterCompile (redefinition)
100 +
101 + Calls the PackCustomAction target after compiling.
102 + Overrides the empty AfterCompile target from Microsoft.Common.targets.
103 +
104 + ==================================================================================================
105 + -->
106 + <Target Name="AfterCompile"
107 + DependsOnTargets="PackCustomAction" />
108 +
109 + <!--
110 + ==================================================================================================
111 + BeforeClean (redefinition)
112 +
113 + Calls the CleanCustomAction target before cleaning.
114 + Overrides the empty AfterCompile target from Microsoft.Common.targets.
115 +
116 + ==================================================================================================
117 + -->
118 + <Target Name="BeforeClean"
119 + DependsOnTargets="CleanCustomAction" />
120 +
121 + <Import Project="$(CustomAfterWixCATargets)" Condition=" '$(CustomAfterWixCATargets)' != '' and Exists('$(CustomAfterWixCATargets)')" />
122 +
123 +</Project>
src/dtf/WixToolset.Dtf.Resources/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Diagnostics.CodeAnalysis;
4 +
5 +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "WixToolset.Dtf.Resources.ResourceCollection.#System.Collections.Generic.ICollection`1<WixToolset.Dtf.Resources.Resource>.IsReadOnly")]
src/dtf/WixToolset.Dtf.Resources/BitmapResource.cs new
+57
@@ -0,0 +1,57 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + /// <summary>
9 + /// A subclass of Resource which provides specific methods for manipulating the resource data.
10 + /// </summary>
11 + /// <remarks>
12 + /// The resource is of type <see cref="ResourceType.Bitmap"/> (RT_GROUPICON).
13 + /// </remarks>
14 + public sealed class BitmapResource : Resource
15 + {
16 + private const int SizeOfBitmapFileHeader = 14; // this is the sizeof(BITMAPFILEHEADER)
17 +
18 + /// <summary>
19 + /// Creates a new BitmapResource object without any data. The data can be later loaded from a file.
20 + /// </summary>
21 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
22 + /// <param name="locale">Locale of the resource</param>
23 + public BitmapResource(string name, int locale)
24 + : this(name, locale, null)
25 + {
26 + }
27 +
28 + /// <summary>
29 + /// Creates a new BitmapResource object with data. The data can be later saved to a file.
30 + /// </summary>
31 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
32 + /// <param name="locale">Locale of the resource</param>
33 + /// <param name="data">Raw resource data</param>
34 + public BitmapResource(string name, int locale, byte[] data)
35 + : base(ResourceType.Bitmap, name, locale, data)
36 + {
37 + }
38 +
39 + /// <summary>
40 + /// Reads the bitmap from a .bmp file.
41 + /// </summary>
42 + /// <param name="path">Path to a bitmap file (.bmp).</param>
43 + public void ReadFromFile(string path)
44 + {
45 + using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
46 + {
47 + // Move past the BITMAPFILEHEADER, and copy the rest of the bitmap as the resource data. Resource
48 + // functions expect only the BITMAPINFO struct which exists just beyond the BITMAPFILEHEADER
49 + // struct in bitmap files.
50 + fs.Seek(BitmapResource.SizeOfBitmapFileHeader, SeekOrigin.Begin);
51 +
52 + base.Data = new byte[fs.Length - BitmapResource.SizeOfBitmapFileHeader];
53 + fs.Read(base.Data, 0, base.Data.Length);
54 + }
55 + }
56 + }
57 +}
src/dtf/WixToolset.Dtf.Resources/FixedFileVersionInfo.cs new
+183
@@ -0,0 +1,183 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Globalization;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + internal class FixedFileVersionInfo
14 + {
15 + public FixedFileVersionInfo()
16 + {
17 + // Set reasonable defaults
18 + this.signature = 0xFEEF04BD;
19 + this.structVersion = 0x00010000; // v1.0
20 + this.FileVersion = new Version(0, 0, 0, 0);
21 + this.ProductVersion = new Version(0, 0, 0, 0);
22 + this.FileFlagsMask = VersionBuildTypes.Debug | VersionBuildTypes.Prerelease;
23 + this.FileFlags = VersionBuildTypes.None;
24 + this.FileOS = VersionFileOS.NT_WINDOWS32;
25 + this.FileType = VersionFileType.Application;
26 + this.FileSubtype = VersionFileSubtype.Unknown;
27 + this.Timestamp = DateTime.MinValue;
28 + }
29 +
30 + private uint signature;
31 + private uint structVersion;
32 +
33 + public Version FileVersion
34 + {
35 + get
36 + {
37 + return this.fileVersion;
38 + }
39 +
40 + set
41 + {
42 + if (value == null)
43 + {
44 + throw new InvalidOperationException();
45 + }
46 +
47 + this.fileVersion = value;
48 + }
49 + }
50 + private Version fileVersion;
51 +
52 + public Version ProductVersion
53 + {
54 + get
55 + {
56 + return this.productVersion;
57 + }
58 +
59 + set
60 + {
61 + if (value == null)
62 + {
63 + throw new InvalidOperationException();
64 + }
65 +
66 + this.productVersion = value;
67 + }
68 + }
69 + private Version productVersion;
70 +
71 + public VersionBuildTypes FileFlagsMask
72 + {
73 + get { return this.fileFlagsMask; }
74 + set { this.fileFlagsMask = value; }
75 + }
76 + private VersionBuildTypes fileFlagsMask;
77 +
78 + public VersionBuildTypes FileFlags
79 + {
80 + get { return this.fileFlags; }
81 + set { this.fileFlags = value; }
82 + }
83 + private VersionBuildTypes fileFlags;
84 +
85 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
86 + public VersionFileOS FileOS
87 + {
88 + get { return this.fileOS; }
89 + set { this.fileOS = value; }
90 + }
91 + private VersionFileOS fileOS;
92 +
93 + public VersionFileType FileType
94 + {
95 + get { return this.fileType; }
96 + set { this.fileType = value; }
97 + }
98 + private VersionFileType fileType;
99 +
100 + public VersionFileSubtype FileSubtype
101 + {
102 + get { return this.fileSubtype; }
103 + set { this.fileSubtype = value; }
104 + }
105 + private VersionFileSubtype fileSubtype;
106 +
107 + public DateTime Timestamp
108 + {
109 + get { return this.timestamp; }
110 + set { this.timestamp = value; }
111 + }
112 + private DateTime timestamp;
113 +
114 + public void Read(BinaryReader reader)
115 + {
116 + this.signature = reader.ReadUInt32();
117 + this.structVersion = reader.ReadUInt32();
118 + this.fileVersion = UInt64ToVersion(reader.ReadUInt64());
119 + this.productVersion = UInt64ToVersion(reader.ReadUInt64());
120 + this.fileFlagsMask = (VersionBuildTypes) reader.ReadInt32();
121 + this.fileFlags = (VersionBuildTypes) reader.ReadInt32();
122 + this.fileOS = (VersionFileOS) reader.ReadInt32();
123 + this.fileType = (VersionFileType) reader.ReadInt32();
124 + this.fileSubtype = (VersionFileSubtype) reader.ReadInt32();
125 + this.timestamp = UInt64ToDateTime(reader.ReadUInt64());
126 + }
127 +
128 + public void Write(BinaryWriter writer)
129 + {
130 + writer.Write(this.signature);
131 + writer.Write(this.structVersion);
132 + writer.Write(VersionToUInt64(this.fileVersion));
133 + writer.Write(VersionToUInt64(this.productVersion));
134 + writer.Write((int) this.fileFlagsMask);
135 + writer.Write((int) this.fileFlags);
136 + writer.Write((int) this.fileOS);
137 + writer.Write((int) this.fileType);
138 + writer.Write((int) this.fileSubtype);
139 + writer.Write(DateTimeToUInt64(this.timestamp));
140 + }
141 +
142 + public static explicit operator FixedFileVersionInfo(byte[] bytesValue)
143 + {
144 + FixedFileVersionInfo ffviValue = new FixedFileVersionInfo();
145 + using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
146 + {
147 + ffviValue.Read(reader);
148 + }
149 + return ffviValue;
150 + }
151 +
152 + public static explicit operator byte[](FixedFileVersionInfo ffviValue)
153 + {
154 + const int FFVI_LENGTH = 52;
155 +
156 + byte[] bytesValue = new byte[FFVI_LENGTH];
157 + using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
158 + {
159 + ffviValue.Write(writer);
160 + }
161 + return bytesValue;
162 + }
163 +
164 + private static Version UInt64ToVersion(ulong version)
165 + {
166 + return new Version((int) ((version >> 16) & 0xFFFF), (int) (version & 0xFFFF), (int) (version >> 48), (int) ((version >> 32) & 0xFFFF));
167 + }
168 + private static ulong VersionToUInt64(Version version)
169 + {
170 + return (((ulong) (ushort) version.Major) << 16) | ((ulong) (ushort) version.Minor)
171 + | (((ulong) (ushort) version.Build) << 48) | (((ulong) (ushort) version.Revision) << 32);
172 + }
173 +
174 + private static DateTime UInt64ToDateTime(ulong dateTime)
175 + {
176 + return (dateTime == 0 ? DateTime.MinValue : DateTime.FromFileTime((long) dateTime));
177 + }
178 + private static ulong DateTimeToUInt64(DateTime dateTime)
179 + {
180 + return (dateTime == DateTime.MinValue ? 0 : (ulong) dateTime.ToFileTime());
181 + }
182 + }
183 +}
src/dtf/WixToolset.Dtf.Resources/GroupIconInfo.cs new
+119
@@ -0,0 +1,119 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Globalization;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + internal enum GroupIconType
14 + {
15 + Unknown,
16 + Icon,
17 + Cursor,
18 + }
19 +
20 + internal struct GroupIconDirectoryInfo
21 + {
22 + public byte width;
23 + public byte height;
24 + public byte colors;
25 + public byte reserved;
26 + public ushort planes;
27 + public ushort bitsPerPixel;
28 + public uint imageSize;
29 + public uint imageOffset; // only valid when icon group is read from .ico file.
30 + public ushort imageIndex; // only valid when icon group is read from PE resource.
31 + }
32 +
33 + internal class GroupIconInfo
34 + {
35 + private ushort reserved;
36 + private GroupIconType type;
37 + private GroupIconDirectoryInfo[] images;
38 +
39 + public GroupIconInfo()
40 + {
41 + this.images = new GroupIconDirectoryInfo[0];
42 + }
43 +
44 + public GroupIconDirectoryInfo[] DirectoryInfo { get { return this.images; } }
45 +
46 + public void ReadFromFile(Stream stream)
47 + {
48 + BinaryReader reader = new BinaryReader(stream);
49 + this.Read(reader, true);
50 + }
51 +
52 + public void ReadFromResource(byte[] data)
53 + {
54 + using (BinaryReader reader = new BinaryReader(new MemoryStream(data, false)))
55 + {
56 + this.Read(reader, false);
57 + }
58 + }
59 +
60 + public byte[] GetResourceData()
61 + {
62 + byte[] data = null;
63 +
64 + using (MemoryStream stream = new MemoryStream())
65 + {
66 + BinaryWriter writer = new BinaryWriter(stream);
67 + writer.Write(this.reserved);
68 + writer.Write((ushort)this.type);
69 + writer.Write((ushort)this.images.Length);
70 + for (int i = 0; i < this.images.Length; ++i)
71 + {
72 + writer.Write(this.images[i].width);
73 + writer.Write(this.images[i].height);
74 + writer.Write(this.images[i].colors);
75 + writer.Write(this.images[i].reserved);
76 + writer.Write(this.images[i].planes);
77 + writer.Write(this.images[i].bitsPerPixel);
78 + writer.Write(this.images[i].imageSize);
79 + writer.Write(this.images[i].imageIndex);
80 + }
81 +
82 + data = new byte[stream.Length];
83 + stream.Seek(0, SeekOrigin.Begin);
84 + stream.Read(data, 0, data.Length);
85 + }
86 +
87 + return data;
88 + }
89 +
90 + private void Read(BinaryReader reader, bool readFromFile)
91 + {
92 + this.reserved = reader.ReadUInt16();
93 + this.type = (GroupIconType)reader.ReadUInt16();
94 +
95 + int imageCount = reader.ReadUInt16();
96 + this.images = new GroupIconDirectoryInfo[imageCount];
97 + for (int i = 0; i < imageCount; ++i)
98 + {
99 + this.images[i].width = reader.ReadByte();
100 + this.images[i].height = reader.ReadByte();
101 + this.images[i].colors = reader.ReadByte();
102 + this.images[i].reserved = reader.ReadByte();
103 + this.images[i].planes = reader.ReadUInt16();
104 + this.images[i].bitsPerPixel = reader.ReadUInt16();
105 + this.images[i].imageSize = reader.ReadUInt32();
106 + if (readFromFile)
107 + {
108 + this.images[i].imageOffset = reader.ReadUInt32();
109 + this.images[i].imageIndex = (ushort)(i + 1);
110 + }
111 + else
112 + {
113 + this.images[i].imageIndex = reader.ReadUInt16();
114 + }
115 + }
116 + }
117 +
118 + }
119 +}
src/dtf/WixToolset.Dtf.Resources/GroupIconResource.cs new
+120
@@ -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 +
3 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// A subclass of Resource which provides specific methods for manipulating the resource data.
16 + /// </summary>
17 + /// <remarks>
18 + /// The resource is of type <see cref="ResourceType.GroupIcon"/> (RT_GROUPICON).
19 + /// </remarks>
20 + public sealed class GroupIconResource : Resource
21 + {
22 + internal bool dirty;
23 + private GroupIconInfo rawGroupIconInfo;
24 + private List<Resource> icons;
25 +
26 + /// <summary>
27 + /// Creates a new GroupIconResource object without any data. The data can be later loaded from a file.
28 + /// </summary>
29 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
30 + /// <param name="locale">Locale of the resource</param>
31 + public GroupIconResource(string name, int locale)
32 + : this(name, locale, null)
33 + {
34 + }
35 +
36 + /// <summary>
37 + /// Creates a new GroupIconResource object with data. The data can be later saved to a file.
38 + /// </summary>
39 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
40 + /// <param name="locale">Locale of the resource</param>
41 + /// <param name="data">Raw resource data</param>
42 + public GroupIconResource(string name, int locale, byte[] data)
43 + : base(ResourceType.GroupIcon, name, locale, data)
44 + {
45 + this.RefreshIconGroupInfo(data);
46 + }
47 +
48 + /// <summary>
49 + /// Gets or sets the raw data of the resource. The data is in the format of the RT_GROUPICON resource structure.
50 + /// </summary>
51 + public override byte[] Data
52 + {
53 + get
54 + {
55 + if (this.dirty)
56 + {
57 + base.Data = this.rawGroupIconInfo.GetResourceData();
58 + this.dirty = false;
59 + }
60 +
61 + return base.Data;
62 + }
63 + set
64 + {
65 + this.RefreshIconGroupInfo(value);
66 +
67 + base.Data = value;
68 + this.dirty = false;
69 + }
70 + }
71 +
72 + /// <summary>
73 + /// Enumerates the the icons in the icon group.
74 + /// </summary>
75 + public IEnumerable<Resource> Icons { get { return this.icons; } }
76 +
77 + /// <summary>
78 + /// Reads the icon group from a .ico file.
79 + /// </summary>
80 + /// <param name="path">Path to an icon file (.ico).</param>
81 + public void ReadFromFile(string path)
82 + {
83 + this.rawGroupIconInfo = new GroupIconInfo();
84 + this.icons = new List<Resource>();
85 + using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
86 + {
87 + this.rawGroupIconInfo.ReadFromFile(fs);
88 +
89 + // After reading the group icon info header from the file, read all the icons.
90 + for (int i = 0; i < this.rawGroupIconInfo.DirectoryInfo.Length; ++i)
91 + {
92 + ushort index = this.rawGroupIconInfo.DirectoryInfo[i].imageIndex;
93 + uint offset = this.rawGroupIconInfo.DirectoryInfo[i].imageOffset;
94 + uint size = this.rawGroupIconInfo.DirectoryInfo[i].imageSize;
95 + byte[] data = new byte[size];
96 +
97 + fs.Seek(offset, SeekOrigin.Begin);
98 + fs.Read(data, 0, data.Length);
99 +
100 + Resource resource = new Resource(ResourceType.Icon, String.Concat("#", index), this.Locale, data);
101 + this.icons.Add(resource);
102 + }
103 + }
104 +
105 + this.dirty = true;
106 + }
107 +
108 + private void RefreshIconGroupInfo(byte[] refreshData)
109 + {
110 + this.rawGroupIconInfo = new GroupIconInfo();
111 + this.icons = new List<Resource>();
112 + if (refreshData != null)
113 + {
114 + this.rawGroupIconInfo.ReadFromResource(refreshData);
115 + }
116 +
117 + this.dirty = true;
118 + }
119 + }
120 +}
src/dtf/WixToolset.Dtf.Resources/NativeMethods.cs new
+55
@@ -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 +
3 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Globalization;
11 + using System.Runtime.InteropServices;
12 +
13 + internal static class NativeMethods
14 + {
15 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
16 + internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
17 + internal const uint LOAD_LIBRARY_AS_DATAFILE = 2;
18 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
19 + internal static extern bool FreeLibrary(IntPtr module);
20 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
21 + internal static extern bool EnumResourceTypes(IntPtr module, EnumResTypesProc enumFunc, IntPtr param);
22 + [return: MarshalAs(UnmanagedType.Bool)]
23 + internal delegate bool EnumResTypesProc(IntPtr module, IntPtr type, IntPtr param);
24 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
25 + internal static extern bool EnumResourceNames(IntPtr module, IntPtr type, EnumResNamesProc enumFunc, IntPtr param);
26 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
27 + internal static extern bool EnumResourceNames(IntPtr module, string type, EnumResNamesProc enumFunc, IntPtr param);
28 + [return: MarshalAs(UnmanagedType.Bool)]
29 + internal delegate bool EnumResNamesProc(IntPtr module, IntPtr type, IntPtr name, IntPtr param);
30 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
31 + internal static extern bool EnumResourceLanguages(IntPtr module, IntPtr type, IntPtr name, EnumResLangsProc enumFunc, IntPtr param);
32 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
33 + internal static extern bool EnumResourceLanguages(IntPtr module, string type, string name, EnumResLangsProc enumFunc, IntPtr param);
34 + [return: MarshalAs(UnmanagedType.Bool)]
35 + internal delegate bool EnumResLangsProc(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param);
36 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
37 + internal static extern IntPtr FindResourceEx(IntPtr module, string type, string name, ushort langId);
38 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
39 + internal static extern IntPtr LoadResource(IntPtr module, IntPtr resourceInfo);
40 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
41 + internal static extern IntPtr LockResource(IntPtr resourceData);
42 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
43 + internal static extern uint SizeofResource(IntPtr module, IntPtr resourceInfo);
44 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
45 + internal static extern IntPtr BeginUpdateResource(string fileName, [MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources);
46 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
47 + internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, IntPtr name, ushort lcid, IntPtr data, uint dataSize);
48 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
49 + internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, string name, ushort lcid, IntPtr data, uint dataSize);
50 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
51 + internal static extern bool UpdateResource(IntPtr updateHandle, string type, string name, ushort lcid, IntPtr data, uint dataSize);
52 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
53 + internal static extern bool EndUpdateResource(IntPtr updateHandle, [MarshalAs(UnmanagedType.Bool)] bool discardChanges);
54 + }
55 +}
src/dtf/WixToolset.Dtf.Resources/Resource.cs new
+225
@@ -0,0 +1,225 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Globalization;
11 + using System.Runtime.InteropServices;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// Represents a Win32 resource which can be loaded from and saved to a PE file.
16 + /// </summary>
17 + public class Resource
18 + {
19 + private ResourceType type;
20 + private string name;
21 + private int locale;
22 + private byte[] data;
23 +
24 + /// <summary>
25 + /// Creates a new Resource object without any data. The data can be later loaded from a file.
26 + /// </summary>
27 + /// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
28 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
29 + /// <param name="locale">Locale of the resource</param>
30 + public Resource(ResourceType type, string name, int locale)
31 + : this(type, name, locale, null)
32 + {
33 + }
34 +
35 + /// <summary>
36 + /// Creates a new Resource object with data. The data can be later saved to a file.
37 + /// </summary>
38 + /// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
39 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
40 + /// <param name="locale">Locale of the resource</param>
41 + /// <param name="data">Raw resource data</param>
42 + public Resource(ResourceType type, string name, int locale, byte[] data)
43 + {
44 + if (name == null)
45 + {
46 + throw new ArgumentNullException("name");
47 + }
48 +
49 + this.type = type;
50 + this.name = name;
51 + this.locale = locale;
52 + this.data = data;
53 + }
54 +
55 + /// <summary>
56 + /// Gets or sets the type of the resource. This may be one of the ResourceType constants
57 + /// or a user-defined type name.
58 + /// </summary>
59 + public ResourceType ResourceType
60 + {
61 + get { return this.type; }
62 + set { this.type = value; }
63 + }
64 +
65 + /// <summary>
66 + /// Gets or sets the name of the resource. For a numeric resource identifier, the decimal number is prefixed with a "#".
67 + /// </summary>
68 + public string Name
69 + {
70 + get
71 + {
72 + return this.name;
73 + }
74 +
75 + set
76 + {
77 + if (value == null)
78 + {
79 + throw new ArgumentNullException("value");
80 + }
81 +
82 + this.name = value;
83 + }
84 + }
85 +
86 + /// <summary>
87 + /// Gets or sets the locale of the resource.
88 + /// </summary>
89 + public int Locale
90 + {
91 + get { return this.locale; }
92 + set { this.locale = value; }
93 + }
94 +
95 + /// <summary>
96 + /// Gets or sets the raw data of the resource.
97 + /// </summary>
98 + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
99 + public virtual byte[] Data
100 + {
101 + get { return this.data; }
102 + set { this.data = value; }
103 + }
104 +
105 + /// <summary>
106 + /// Loads the resource data from a file. The file is searched for a resource with matching type, name, and locale.
107 + /// </summary>
108 + /// <param name="file">Win32 PE file containing the resource</param>
109 + public void Load(string file)
110 + {
111 + IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
112 + try
113 + {
114 + this.Load(module);
115 + }
116 + finally
117 + {
118 + NativeMethods.FreeLibrary(module);
119 + }
120 + }
121 +
122 + internal void Load(IntPtr module)
123 + {
124 + IntPtr resourceInfo = NativeMethods.FindResourceEx(module, (string) this.ResourceType, this.Name, (ushort) this.Locale);
125 + if (resourceInfo != IntPtr.Zero)
126 + {
127 + uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo);
128 + IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo);
129 + IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
130 + byte[] resourceBytes = new byte[resourceLength];
131 + Marshal.Copy(resourcePtr, resourceBytes, 0, resourceBytes.Length);
132 + this.Data = resourceBytes;
133 + }
134 + else
135 + {
136 + this.Data = null;
137 + }
138 + }
139 +
140 + /// <summary>
141 + /// Saves the resource to a file. Any existing resource data with matching type, name, and locale is overwritten.
142 + /// </summary>
143 + /// <param name="file">Win32 PE file to contain the resource</param>
144 + public void Save(string file)
145 + {
146 + IntPtr updateHandle = IntPtr.Zero;
147 + try
148 + {
149 + updateHandle = NativeMethods.BeginUpdateResource(file, false);
150 + this.Save(updateHandle);
151 + if (!NativeMethods.EndUpdateResource(updateHandle, false))
152 + {
153 + int err = Marshal.GetLastWin32Error();
154 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error code: {0}", err));
155 + }
156 + updateHandle = IntPtr.Zero;
157 + }
158 + finally
159 + {
160 + if (updateHandle != IntPtr.Zero)
161 + {
162 + NativeMethods.EndUpdateResource(updateHandle, true);
163 + }
164 + }
165 + }
166 +
167 + internal void Save(IntPtr updateHandle)
168 + {
169 + IntPtr dataPtr = IntPtr.Zero;
170 + try
171 + {
172 + int dataLength = 0;
173 + if (this.Data != null)
174 + {
175 + dataLength = this.Data.Length;
176 + dataPtr = Marshal.AllocHGlobal(dataLength);
177 + Marshal.Copy(this.Data, 0, dataPtr, dataLength);
178 + }
179 + bool updateSuccess;
180 + if (this.Name.StartsWith("#", StringComparison.Ordinal))
181 + {
182 + // A numeric-named resource must be saved via the integer version of UpdateResource.
183 + IntPtr intName = new IntPtr(Int32.Parse(this.Name.Substring(1), CultureInfo.InvariantCulture));
184 + updateSuccess = NativeMethods.UpdateResource(updateHandle, new IntPtr(this.ResourceType.IntegerValue), intName, (ushort) this.Locale, dataPtr, (uint) dataLength);
185 + }
186 + else
187 + {
188 + updateSuccess = NativeMethods.UpdateResource(updateHandle, (string) this.ResourceType, this.Name, (ushort) this.Locale, dataPtr, (uint) dataLength);
189 + }
190 + if (!updateSuccess)
191 + {
192 + throw new IOException("Failed to save resource. Error: " + Marshal.GetLastWin32Error());
193 + }
194 + }
195 + finally
196 + {
197 + if (dataPtr != IntPtr.Zero)
198 + {
199 + Marshal.FreeHGlobal(dataPtr);
200 + }
201 + }
202 + }
203 +
204 + /// <summary>
205 + /// Tests if type, name, and locale of this Resource object match another Resource object.
206 + /// </summary>
207 + /// <param name="obj">Resource object to be compared</param>
208 + /// <returns>True if the objects represent the same resource; false otherwise.</returns>
209 + public override bool Equals(object obj)
210 + {
211 + Resource res = obj as Resource;
212 + if (res == null) return false;
213 + return this.ResourceType == res.ResourceType && this.Name == res.Name && this.Locale == res.Locale;
214 + }
215 +
216 + /// <summary>
217 + /// Gets a hash code for this Resource object.
218 + /// </summary>
219 + /// <returns>Hash code generated from the resource type, name, and locale.</returns>
220 + public override int GetHashCode()
221 + {
222 + return this.ResourceType.GetHashCode() ^ this.Name.GetHashCode() ^ this.Locale.GetHashCode();
223 + }
224 + }
225 +}
src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs new
+340
@@ -0,0 +1,340 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Runtime.InteropServices;
13 + using System.Diagnostics.CodeAnalysis;
14 +
15 + /// <summary>
16 + /// Allows reading and editing of resource data in a Win32 PE file.
17 + /// </summary>
18 + /// <remarks>
19 + /// To use this class:<list type="number">
20 + /// <item>Create a new ResourceCollection</item>
21 + /// <item>Locate resources for the collection by calling one of the <see cref="ResourceCollection.Find(string)"/> methods</item>
22 + /// <item>Load data of one or more <see cref="Resource"/>s from a file by calling the <see cref="Load"/> method of the
23 + /// Resource class, or load them all at once (more efficient) with the <see cref="Load"/> method of the ResourceCollection.</item>
24 + /// <item>Read and/or edit data of the individual Resource objects using the methods on that class.</item>
25 + /// <item>Save data of one or more <see cref="Resource"/>s to a file by calling the <see cref="Save"/> method of the
26 + /// Resource class, or save them all at once (more efficient) with the <see cref="Save"/> method of the ResourceCollection.</item>
27 + /// </list>
28 + /// </remarks>
29 + public class ResourceCollection : ICollection<Resource>
30 + {
31 + private List<Resource> resources;
32 +
33 + /// <summary>
34 + /// Creates a new, empty ResourceCollection.
35 + /// </summary>
36 + public ResourceCollection()
37 + {
38 + this.resources = new List<Resource>();
39 + }
40 +
41 + /// <summary>
42 + /// Locates all resources in a file, including all resource types and languages. For each located resource,
43 + /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
44 + /// </summary>
45 + /// <param name="resFile">The file to be searched for resources.</param>
46 + /// <exception cref="IOException">resources could not be read from the file</exception>
47 + public void Find(string resFile)
48 + {
49 + this.Clear();
50 +
51 + IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
52 + if (module == IntPtr.Zero)
53 + {
54 + int err = Marshal.GetLastWin32Error();
55 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err));
56 + }
57 + try
58 + {
59 + if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero))
60 + {
61 + int err = Marshal.GetLastWin32Error();
62 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err));
63 + }
64 + }
65 + finally
66 + {
67 + NativeMethods.FreeLibrary(module);
68 + }
69 + }
70 +
71 + /// <summary>
72 + /// Locates all resources in a file of a given type, including all languages. For each located resource,
73 + /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
74 + /// </summary>
75 + /// <param name="resFile">The file to be searched for resources.</param>
76 + /// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
77 + /// <exception cref="IOException">resources could not be read from the file</exception>
78 + public void Find(string resFile, ResourceType type)
79 + {
80 + this.Clear();
81 +
82 + IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
83 + try
84 + {
85 + if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero))
86 + {
87 + int err = Marshal.GetLastWin32Error();
88 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err));
89 + }
90 + }
91 + finally
92 + {
93 + NativeMethods.FreeLibrary(module);
94 + }
95 + }
96 +
97 + /// <summary>
98 + /// Locates all resources in a file of a given type and language. For each located resource,
99 + /// a <see cref="Resource"/> instance (or subclass) is added to the collection.
100 + /// </summary>
101 + /// <param name="resFile">The file to be searched for resources.</param>
102 + /// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
103 + /// <param name="name">The name of the resource to search for.</param>
104 + /// <exception cref="IOException">resources could not be read from the file</exception>
105 + public void Find(string resFile, ResourceType type, string name)
106 + {
107 + this.Clear();
108 +
109 + IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
110 + try
111 + {
112 + if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero))
113 + {
114 + int err = Marshal.GetLastWin32Error();
115 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
116 + }
117 + }
118 + finally
119 + {
120 + NativeMethods.FreeLibrary(module);
121 + }
122 + }
123 +
124 + private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param)
125 + {
126 + if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(EnumResNames), IntPtr.Zero))
127 + {
128 + int err = Marshal.GetLastWin32Error();
129 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err));
130 + }
131 + return true;
132 + }
133 +
134 + private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param)
135 + {
136 + if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(EnumResLangs), IntPtr.Zero))
137 + {
138 + int err = Marshal.GetLastWin32Error();
139 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
140 + }
141 + return true;
142 + }
143 +
144 + private bool EnumResLangs(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param)
145 + {
146 + Resource res;
147 + if (((int) type) == ResourceType.Version.IntegerValue)
148 + {
149 + res = new VersionResource(ResourceNameToString(name), langId);
150 + }
151 + else
152 + {
153 + res = new Resource(ResourceNameToString(type), ResourceNameToString(name), langId);
154 + }
155 +
156 + if (!this.Contains(res))
157 + {
158 + this.Add(res);
159 + }
160 +
161 + return true;
162 + }
163 +
164 + private static string ResourceNameToString(IntPtr resName)
165 + {
166 + if ((resName.ToInt64() >> 16) == 0)
167 + {
168 + return "#" + resName.ToString();
169 + }
170 + else
171 + {
172 + return Marshal.PtrToStringAuto(resName);
173 + }
174 + }
175 +
176 + /// <summary>
177 + /// For all resources in the collection, loads their data from a resource file.
178 + /// </summary>
179 + /// <param name="file">The file from which resources are loaded.</param>
180 + public void Load(string file)
181 + {
182 + IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
183 + try
184 + {
185 + foreach (Resource res in this)
186 + {
187 + res.Load(module);
188 + }
189 + }
190 + finally
191 + {
192 + NativeMethods.FreeLibrary(module);
193 + }
194 + }
195 +
196 + /// <summary>
197 + /// For all resources in the collection, saves their data to a resource file.
198 + /// </summary>
199 + /// <param name="file">The file to which resources are saved.</param>
200 + public void Save(string file)
201 + {
202 + IntPtr updateHandle = IntPtr.Zero;
203 + try
204 + {
205 + updateHandle = NativeMethods.BeginUpdateResource(file, false);
206 + foreach (Resource res in this)
207 + {
208 + res.Save(updateHandle);
209 + }
210 + if (!NativeMethods.EndUpdateResource(updateHandle, false))
211 + {
212 + int err = Marshal.GetLastWin32Error();
213 + throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err));
214 + }
215 + updateHandle = IntPtr.Zero;
216 + }
217 + finally
218 + {
219 + if (updateHandle != IntPtr.Zero)
220 + {
221 + NativeMethods.EndUpdateResource(updateHandle, true);
222 + }
223 + }
224 + }
225 +
226 + /// <summary>
227 + /// Gets or sets the element at the specified index.
228 + /// </summary>
229 + public Resource this[int index]
230 + {
231 + get
232 + {
233 + return (Resource) this.resources[index];
234 + }
235 + set
236 + {
237 + this.resources[index] = value;
238 + }
239 + }
240 +
241 + /// <summary>
242 + /// Adds a new item to the collection.
243 + /// </summary>
244 + /// <param name="item">The Resource to add.</param>
245 + public void Add(Resource item)
246 + {
247 + this.resources.Add(item);
248 + }
249 +
250 + /// <summary>
251 + /// Removes an item to the collection.
252 + /// </summary>
253 + /// <param name="item">The Resource to remove.</param>
254 + public bool Remove(Resource item)
255 + {
256 + return this.resources.Remove(item);
257 + }
258 +
259 + /// <summary>
260 + /// Gets the index of an item in the collection.
261 + /// </summary>
262 + /// <param name="item">The Resource to search for.</param>
263 + /// <returns>The index of the item, or -1 if not found.</returns>
264 + public int IndexOf(Resource item)
265 + {
266 + return this.resources.IndexOf(item);
267 + }
268 +
269 + /// <summary>
270 + /// Inserts a item into the collection.
271 + /// </summary>
272 + /// <param name="index">The insertion index.</param>
273 + /// <param name="item">The Resource to insert.</param>
274 + public void Insert(int index, Resource item)
275 + {
276 + this.resources.Insert(index, item);
277 + }
278 +
279 + /// <summary>
280 + /// Tests if the collection contains an item.
281 + /// </summary>
282 + /// <param name="item">The Resource to search for.</param>
283 + /// <returns>true if the item is found; false otherwise</returns>
284 + public bool Contains(Resource item)
285 + {
286 + return this.resources.Contains(item);
287 + }
288 +
289 + /// <summary>
290 + /// Copies the collection into an array.
291 + /// </summary>
292 + /// <param name="array">The array to copy into.</param>
293 + /// <param name="arrayIndex">The starting index in the destination array.</param>
294 + public void CopyTo(Resource[] array, int arrayIndex)
295 + {
296 + this.resources.CopyTo(array, arrayIndex);
297 + }
298 +
299 + /// <summary>
300 + /// Removes all resources from the collection.
301 + /// </summary>
302 + public void Clear()
303 + {
304 + this.resources.Clear();
305 + }
306 +
307 + /// <summary>
308 + /// Gets the number of resources in the collection.
309 + /// </summary>
310 + public int Count
311 + {
312 + get
313 + {
314 + return this.resources.Count;
315 + }
316 + }
317 +
318 + /// <summary>
319 + /// Gets an enumerator over all resources in the collection.
320 + /// </summary>
321 + /// <returns></returns>
322 + public IEnumerator<Resource> GetEnumerator()
323 + {
324 + return this.resources.GetEnumerator();
325 + }
326 +
327 + IEnumerator IEnumerable.GetEnumerator()
328 + {
329 + return ((IEnumerable) this.resources).GetEnumerator();
330 + }
331 +
332 + bool ICollection<Resource>.IsReadOnly
333 + {
334 + get
335 + {
336 + return false;
337 + }
338 + }
339 + }
340 +}
src/dtf/WixToolset.Dtf.Resources/ResourceType.cs new
+198
@@ -0,0 +1,198 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.Diagnostics.CodeAnalysis;
7 + using System.Globalization;
8 +
9 + /// <summary>
10 + /// Represents either a standard integer resource type or a custom resource type name.
11 + /// </summary>
12 + public class ResourceType
13 + {
14 + // Silence warnings about doc-comments
15 + #pragma warning disable 1591
16 +
17 + public static ResourceType None { get { return "#0"; } }
18 + public static ResourceType Cursor { get { return "#1"; } }
19 + public static ResourceType Bitmap { get { return "#2"; } }
20 + public static ResourceType Icon { get { return "#3"; } }
21 + public static ResourceType Menu { get { return "#4"; } }
22 + public static ResourceType Dialog { get { return "#5"; } }
23 + public static ResourceType String { get { return "#6"; } }
24 + public static ResourceType FontDir { get { return "#7"; } }
25 + public static ResourceType Font { get { return "#8"; } }
26 + public static ResourceType Accelerator { get { return "#9"; } }
27 + public static ResourceType RCData { get { return "#10"; } }
28 + public static ResourceType MessageTable { get { return "#11"; } }
29 + public static ResourceType GroupCursor { get { return "#12"; } }
30 + public static ResourceType GroupIcon { get { return "#14"; } }
31 + public static ResourceType Version { get { return "#16"; } }
32 + public static ResourceType DialogInclude { get { return "#17"; } }
33 + public static ResourceType PlugPlay { get { return "#19"; } }
34 + public static ResourceType Vxd { get { return "#20"; } }
35 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
36 + public static ResourceType AniCursor { get { return "#21"; } }
37 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
38 + public static ResourceType AniIcon { get { return "#22"; } }
39 + public static ResourceType Html { get { return "#23"; } }
40 + public static ResourceType Manifest { get { return "#24"; } }
41 +
42 + #pragma warning restore 1591
43 +
44 + private string resourceType;
45 +
46 + /// <summary>
47 + /// Creates a new resource type from a string resource name.
48 + /// </summary>
49 + /// <param name="resourceType">String resource name,
50 + /// or an integer resource type prefixed by a #.</param>
51 + public ResourceType(string resourceType)
52 + {
53 + if (string.IsNullOrEmpty(resourceType))
54 + {
55 + throw new ArgumentNullException("resourceType");
56 + }
57 +
58 + this.resourceType = resourceType;
59 +
60 + if (this.IsInteger && this.IntegerValue < 0)
61 + {
62 + throw new ArgumentOutOfRangeException("Invalid integer resource type value.");
63 + }
64 + }
65 +
66 + /// <summary>
67 + /// Creates a new integer resource type.
68 + /// </summary>
69 + /// <param name="resourceType">Integer value of a well-known resource type.</param>
70 + public ResourceType(int resourceType)
71 + : this("#" + resourceType)
72 + {
73 + }
74 +
75 + /// <summary>
76 + /// Gets a flag indicating whether the resource type is an integer type.
77 + /// </summary>
78 + public bool IsInteger
79 + {
80 + get
81 + {
82 + return this.resourceType.StartsWith("#", StringComparison.Ordinal);
83 + }
84 + }
85 +
86 + /// <summary>
87 + /// Gets the integer value of the resource type, or -1 if the resource type is not an integer.
88 + /// </summary>
89 + public int IntegerValue
90 + {
91 + get
92 + {
93 + int value;
94 + if (!this.IsInteger ||
95 + !Int32.TryParse(this.resourceType.Substring(1), out value))
96 + {
97 + value = -1;
98 + }
99 +
100 + return value;
101 + }
102 + }
103 +
104 + /// <summary>
105 + /// Gets a string representation of the resource type.
106 + /// </summary>
107 + /// <returns>The custom resource name, or the name of a well-known resource type.</returns>
108 + public override string ToString()
109 + {
110 + if (this.IsInteger)
111 + {
112 + switch (this.IntegerValue)
113 + {
114 + case 0: return "None";
115 + case 1: return "Cursor";
116 + case 2: return "Bitmap";
117 + case 3: return "Icon";
118 + case 4: return "Menu";
119 + case 5: return "Dialog";
120 + case 6: return "String";
121 + case 7: return "FontDir";
122 + case 8: return "Font";
123 + case 9: return "Accelerator";
124 + case 10: return "RCData";
125 + case 11: return "MessageTable";
126 + case 12: return "GroupCursor";
127 + case 14: return "GroupIcon";
128 + case 16: return "Version";
129 + case 17: return "DialogInclude";
130 + case 19: return "PlugPlay";
131 + case 20: return "Vxd";
132 + case 21: return "AniCursor";
133 + case 22: return "AniIcon";
134 + case 23: return "Html";
135 + case 24: return "Manifest";
136 + }
137 + }
138 +
139 + return this.resourceType;
140 + }
141 +
142 + /// <summary>
143 + /// Tests whether one resource type equals another object.
144 + /// </summary>
145 + /// <param name="obj">Other object.</param>
146 + /// <returns>True if equal, else false.</returns>
147 + public override bool Equals(object obj)
148 + {
149 + return this.Equals(obj as ResourceType);
150 + }
151 +
152 + /// <summary>
153 + /// Tests whether one resource type equals another.
154 + /// </summary>
155 + /// <param name="otherType">Other resource type.</param>
156 + /// <returns>True if equal, else false.</returns>
157 + public bool Equals(ResourceType otherType)
158 + {
159 + return otherType != null && this.resourceType.Equals(otherType.resourceType, StringComparison.Ordinal);
160 + }
161 +
162 + /// <summary>
163 + /// Gets a hash code suitable for using the resource type as a dictionary key.
164 + /// </summary>
165 + /// <returns>Hash code based on the resource type string.</returns>
166 + public override int GetHashCode()
167 + {
168 + return this.resourceType.GetHashCode();
169 + }
170 +
171 + /// <summary>
172 + /// Implicitly converts a string to a ResourceType.
173 + /// </summary>
174 + /// <param name="resourceType">String resource type to convert.</param>
175 + /// <returns>ResourceType object.</returns>
176 + public static implicit operator ResourceType(string resourceType)
177 + {
178 + return new ResourceType(resourceType);
179 + }
180 +
181 + /// <summary>
182 + /// Explicitly converts a ResourceType to a string.
183 + /// </summary>
184 + /// <param name="resourceType">ResourceType object to convert.</param>
185 + /// <returns>The resource type string.</returns>
186 + /// <remarks>
187 + /// Unlike <see cref="ToString" />, this conversion does not return
188 + /// the common name of well-known integer resource types. Therefore,
189 + /// the returned string is suitable for passing directly to Win32
190 + /// resource APIs that accept resource type strings.
191 + /// </remarks>
192 + public static explicit operator string(ResourceType resourceType)
193 + {
194 + return resourceType.resourceType;
195 + }
196 + }
197 +}
198 +
src/dtf/WixToolset.Dtf.Resources/VersionEnums.cs new
+86
@@ -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 +
3 +namespace WixToolset.Dtf.Resources
4 +{
5 + // Silence warnings about doc-comments
6 + #pragma warning disable 1591
7 +
8 + using System;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// Identifies build types of a versioned file.
13 + /// </summary>
14 + [Flags]
15 + public enum VersionBuildTypes : int
16 + {
17 + None = 0x00,
18 + Debug = 0x01,
19 + Prerelease = 0x02,
20 + Patched = 0x04,
21 + PrivateBuild = 0x08,
22 + InfoInferred = 0x10,
23 + SpecialBuild = 0x20,
24 + }
25 +
26 + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
27 + internal enum VersionFileOS : int
28 + {
29 + Unknown = 0x00000000,
30 + DOS = 0x00010000,
31 + OS216 = 0x00020000,
32 + OS232 = 0x00030000,
33 + NT = 0x00040000,
34 + WINCE = 0x00050000,
35 + WINDOWS16 = 0x00000001,
36 + PM16 = 0x00000002,
37 + PM32 = 0x00000003,
38 + WINDOWS32 = 0x00000004,
39 + DOS_WINDOWS16 = 0x00010001,
40 + DOS_WINDOWS32 = 0x00010004,
41 + OS216_PM16 = 0x00020002,
42 + OS232_PM32 = 0x00030003,
43 + NT_WINDOWS32 = 0x00040004,
44 + }
45 +
46 + /// <summary>
47 + /// Identifies the type of a versioned file.
48 + /// </summary>
49 + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
50 + public enum VersionFileType : int
51 + {
52 + Unknown = 0,
53 + Application = 1,
54 + Dll = 2,
55 + Driver = 3,
56 + Font = 4,
57 + VirtualDevice = 5,
58 + StaticLibrary = 7,
59 + }
60 +
61 + /// <summary>
62 + /// Identifies the sub-type of a versioned file.
63 + /// </summary>
64 + public enum VersionFileSubtype : int
65 + {
66 + Unknown = 0,
67 + PrinterDriver = 1,
68 + KeyboardDriver = 2,
69 + LanguageDriver = 3,
70 + DisplayDriver = 4,
71 + MouseDriver = 5,
72 + NetworkDriver = 6,
73 + SystemDriver = 7,
74 + InstallableDriver = 8,
75 + SoundDriver = 9,
76 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Comm")]
77 + CommDriver = 10,
78 + InputMethodDriver = 11,
79 + VersionedPrinterDriver = 12,
80 + RasterFont = 1,
81 + VectorFont = 2,
82 + TrueTypeFont = 3,
83 + }
84 +
85 + #pragma warning restore 1591
86 +}
src/dtf/WixToolset.Dtf.Resources/VersionInfo.cs new
+270
@@ -0,0 +1,270 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 +
13 + internal class VersionInfo : ICollection<VersionInfo>
14 + {
15 + private string key;
16 + private bool isString;
17 + private byte[] data;
18 + private List<VersionInfo> children;
19 +
20 + public VersionInfo(string key)
21 + : base()
22 + {
23 + if (key == null)
24 + {
25 + throw new ArgumentNullException("key");
26 + }
27 +
28 + this.key = key;
29 + this.children = new List<VersionInfo>();
30 + }
31 +
32 + public string Key
33 + {
34 + get
35 + {
36 + return this.key;
37 + }
38 +
39 + set
40 + {
41 + if (value == null)
42 + {
43 + throw new ArgumentNullException("value");
44 + }
45 +
46 + this.key = value;
47 + }
48 + }
49 +
50 + public bool IsString
51 + {
52 + get
53 + {
54 + return this.isString;
55 + }
56 +
57 + set
58 + {
59 + this.isString = value;
60 + }
61 + }
62 +
63 + public byte[] Data
64 + {
65 + get
66 + {
67 + return this.data;
68 + }
69 +
70 + set
71 + {
72 + this.data = value;
73 + this.isString = false;
74 + }
75 + }
76 +
77 + public void Read(BinaryReader reader)
78 + {
79 + long basePosition = reader.BaseStream.Position;
80 + int verInfoSize = (int) reader.ReadUInt16();
81 + int valueSize = (int) reader.ReadUInt16();
82 + bool dataIsString = (reader.ReadUInt16() != 0);
83 + StringBuilder keyStringBuilder = new StringBuilder();
84 + char c;
85 + while ((c = (char) reader.ReadUInt16()) != 0)
86 + {
87 + keyStringBuilder.Append(c);
88 + }
89 + this.Key = keyStringBuilder.ToString();
90 + Pad(reader, basePosition);
91 + if (valueSize == 0)
92 + {
93 + this.data = null;
94 + }
95 + else
96 + {
97 + if (dataIsString) valueSize *= 2; // Count is # of chars instead of bytes
98 + this.data = reader.ReadBytes(valueSize);
99 + this.isString = dataIsString;
100 + Pad(reader, basePosition);
101 + }
102 +
103 + while (reader.BaseStream.Position - basePosition < verInfoSize)
104 + {
105 + Pad(reader, basePosition);
106 + VersionInfo childVerInfo = new VersionInfo("");
107 + childVerInfo.Read(reader);
108 + this.children.Add(childVerInfo);
109 + }
110 + }
111 +
112 + public void Write(BinaryWriter writer)
113 + {
114 + long basePosition = writer.BaseStream.Position;
115 + writer.Write((ushort) this.Length);
116 + byte[] valueBytes = this.data;
117 + writer.Write((ushort) ((valueBytes != null ? valueBytes.Length : 0) / (this.IsString ? 2 : 1)));
118 + writer.Write((ushort) (this.IsString ? 1 : 0));
119 + byte[] keyBytes = new byte[Encoding.Unicode.GetByteCount(this.Key) + 2];
120 + Encoding.Unicode.GetBytes(this.Key, 0, this.Key.Length, keyBytes, 0);
121 + writer.Write(keyBytes);
122 + Pad(writer, basePosition);
123 + if (valueBytes != null)
124 + {
125 + writer.Write(valueBytes);
126 + Pad(writer, basePosition);
127 + }
128 +
129 + foreach (VersionInfo childVersionInfo in this.children)
130 + {
131 + Pad(writer, basePosition);
132 + childVersionInfo.Write(writer);
133 + }
134 + }
135 +
136 + private static void Pad(BinaryReader reader, long basePosition)
137 + {
138 + long position = reader.BaseStream.Position;
139 + int diff = (int) (position - basePosition) % 4;
140 + if (diff > 0) while (diff++ < 4 && reader.BaseStream.Position < reader.BaseStream.Length) reader.ReadByte();
141 + }
142 + private static void Pad(BinaryWriter writer, long basePosition)
143 + {
144 + long position = writer.BaseStream.Position;
145 + int diff = (int) (position - basePosition) % 4;
146 + if (diff > 0) while (diff++ < 4) writer.Write((byte) 0);
147 + }
148 +
149 + private int Length
150 + {
151 + get
152 + {
153 + int len = 6 + Encoding.Unicode.GetByteCount(this.Key) + 2;
154 + if (len % 4 > 0) len += (4 - len % 4);
155 + len += (this.data != null ? this.data.Length : 0);
156 + if (len % 4 > 0) len += (4 - len % 4);
157 + foreach (VersionInfo childVersionInfo in this.children)
158 + {
159 + if (len % 4 > 0) len += (4 - len % 4);
160 + len += childVersionInfo.Length;
161 + }
162 + return len;
163 + }
164 + }
165 +
166 + public static explicit operator VersionInfo(byte[] bytesValue)
167 + {
168 + VersionInfo viValue = new VersionInfo("");
169 + using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
170 + {
171 + viValue.Read(reader);
172 + }
173 + return viValue;
174 + }
175 +
176 + public static explicit operator byte[](VersionInfo viValue)
177 + {
178 + byte[] bytesValue = new byte[viValue.Length];
179 + using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
180 + {
181 + viValue.Write(writer);
182 + }
183 + return bytesValue;
184 + }
185 +
186 + public VersionInfo this[string itemKey]
187 + {
188 + get
189 + {
190 + int index = this.IndexOf(itemKey);
191 + if (index < 0) return null;
192 + return this.children[index];
193 + }
194 + }
195 +
196 + public void Add(VersionInfo item)
197 + {
198 + this.children.Add(item);
199 + }
200 +
201 + public bool Remove(VersionInfo item)
202 + {
203 + return this.children.Remove(item);
204 + }
205 +
206 + public bool Remove(string itemKey)
207 + {
208 + int index = this.IndexOf(itemKey);
209 + if (index >= 0)
210 + {
211 + this.children.RemoveAt(index);
212 + return true;
213 + }
214 + else
215 + {
216 + return false;
217 + }
218 + }
219 +
220 + private int IndexOf(string itemKey)
221 + {
222 + for (int i = 0; i < this.children.Count; i++)
223 + {
224 + if (this.children[i].Key == itemKey) return i;
225 + }
226 + return -1;
227 + }
228 +
229 + public bool Contains(VersionInfo item)
230 + {
231 + return this.children.Contains(item);
232 + }
233 +
234 + public void CopyTo(VersionInfo[] array, int index)
235 + {
236 + this.children.CopyTo(array, index);
237 + }
238 +
239 + public void Clear()
240 + {
241 + this.children.Clear();
242 + }
243 +
244 + public int Count
245 + {
246 + get
247 + {
248 + return this.children.Count;
249 + }
250 + }
251 +
252 + public bool IsReadOnly
253 + {
254 + get
255 + {
256 + return false;
257 + }
258 + }
259 +
260 + public IEnumerator<VersionInfo> GetEnumerator()
261 + {
262 + return this.children.GetEnumerator();
263 + }
264 +
265 + IEnumerator IEnumerable.GetEnumerator()
266 + {
267 + return this.GetEnumerator();
268 + }
269 + }
270 +}
src/dtf/WixToolset.Dtf.Resources/VersionResource.cs new
+415
@@ -0,0 +1,415 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// A subclass of Resource which provides specific methods for manipulating the resource data.
16 + /// </summary>
17 + /// <remarks>
18 + /// The resource is of type <see cref="ResourceType.Version"/> (RT_VERSION).
19 + /// </remarks>
20 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
21 + public sealed class VersionResource : Resource, ICollection<VersionStringTable>
22 + {
23 + internal bool dirty;
24 + private VersionInfo rawVersionInfo;
25 + private FixedFileVersionInfo rawFileVersionInfo;
26 +
27 + /// <summary>
28 + /// Creates a new VersionResource object without any data. The data can be later loaded from a file.
29 + /// </summary>
30 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
31 + /// <param name="locale">Locale of the resource</param>
32 + public VersionResource(string name, int locale)
33 + : this(name, locale, null)
34 + {
35 + }
36 +
37 + /// <summary>
38 + /// Creates a new VersionResource object with data. The data can be later saved to a file.
39 + /// </summary>
40 + /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
41 + /// <param name="locale">Locale of the resource</param>
42 + /// <param name="data">Raw resource data</param>
43 + public VersionResource(string name, int locale, byte[] data)
44 + : base(ResourceType.Version, name, locale, data)
45 + {
46 + this.RefreshVersionInfo(data);
47 + }
48 +
49 + /// <summary>
50 + /// Gets or sets the raw data of the resource. The data is in the format of the VS_VERSIONINFO structure.
51 + /// </summary>
52 + public override byte[] Data
53 + {
54 + get
55 + {
56 + if (this.dirty)
57 + {
58 + this.rawVersionInfo.Data = (byte[]) this.rawFileVersionInfo;
59 + base.Data = (byte[]) this.rawVersionInfo;
60 + this.dirty = false;
61 + }
62 +
63 + return base.Data;
64 + }
65 + set
66 + {
67 + base.Data = value;
68 + this.RefreshVersionInfo(value);
69 + this.dirty = false;
70 + }
71 + }
72 +
73 + private void RefreshVersionInfo(byte[] refreshData)
74 + {
75 + if (refreshData == null)
76 + {
77 + this.rawVersionInfo = new VersionInfo("VS_VERSION_INFO");
78 + this.rawFileVersionInfo = new FixedFileVersionInfo();
79 + }
80 + else
81 + {
82 + this.rawVersionInfo = (VersionInfo) refreshData;
83 + this.rawFileVersionInfo = (FixedFileVersionInfo) this.rawVersionInfo.Data;
84 + }
85 + }
86 +
87 + /// <summary>
88 + /// Gets or sets the binary locale-independent file version of the version resource.
89 + /// </summary>
90 + public Version FileVersion
91 + {
92 + get
93 + {
94 + return this.rawFileVersionInfo.FileVersion;
95 + }
96 + set
97 + {
98 + this.rawFileVersionInfo.FileVersion = value;
99 + this.dirty = true;
100 + }
101 + }
102 +
103 + /// <summary>
104 + /// Gets or sets the binary locale-independent product version of the version resource.
105 + /// </summary>
106 + public Version ProductVersion
107 + {
108 + get
109 + {
110 + return this.rawFileVersionInfo.ProductVersion;
111 + }
112 + set
113 + {
114 + this.rawFileVersionInfo.ProductVersion = value;
115 + this.dirty = true;
116 + }
117 + }
118 +
119 + /// <summary>
120 + /// Gets or sets a bitmask that specifies the build types of the file.
121 + /// </summary>
122 + public VersionBuildTypes BuildTypes
123 + {
124 + get
125 + {
126 + return this.rawFileVersionInfo.FileFlags &
127 + this.rawFileVersionInfo.FileFlagsMask;
128 + }
129 + set
130 + {
131 + this.rawFileVersionInfo.FileFlags = value;
132 + this.rawFileVersionInfo.FileFlagsMask = value;
133 + this.dirty = true;
134 + }
135 + }
136 +
137 + /// <summary>
138 + /// Gets or sets the general type of the file.
139 + /// </summary>
140 + public VersionFileType FileType
141 + {
142 + get
143 + {
144 + return this.rawFileVersionInfo.FileType;
145 + }
146 + set
147 + {
148 + this.rawFileVersionInfo.FileType = value;
149 + this.dirty = true;
150 + }
151 + }
152 +
153 + /// <summary>
154 + /// Gets or sets the specific type of the file.
155 + /// </summary>
156 + public VersionFileSubtype FileSubtype
157 + {
158 + get
159 + {
160 + return this.rawFileVersionInfo.FileSubtype;
161 + }
162 + set
163 + {
164 + this.rawFileVersionInfo.FileSubtype = value;
165 + this.dirty = true;
166 + }
167 + }
168 +
169 + /// <summary>
170 + /// Gets or sets the binary creation date and time.
171 + /// </summary>
172 + public DateTime Timestamp
173 + {
174 + get
175 + {
176 + return this.rawFileVersionInfo.Timestamp;
177 + }
178 + set
179 + {
180 + this.rawFileVersionInfo.Timestamp = value;
181 + this.dirty = true;
182 + }
183 + }
184 +
185 + /// <summary>
186 + /// Gets the string table for a specific locale, or null if there is no table for that locale.
187 + /// </summary>
188 + /// <seealso cref="Add(int)"/>
189 + /// <seealso cref="Remove(int)"/>
190 + public VersionStringTable this[int locale]
191 + {
192 + get
193 + {
194 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
195 + if (svi != null)
196 + {
197 + foreach (VersionInfo strings in svi)
198 + {
199 + int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
200 + if (stringsLocale == locale)
201 + {
202 + return new VersionStringTable(this, strings);
203 + }
204 + }
205 + }
206 + return null;
207 + }
208 + }
209 +
210 + /// <summary>
211 + /// Adds a new version string table for a locale.
212 + /// </summary>
213 + /// <param name="locale">Locale of the table</param>
214 + /// <returns>The new string table, or the existing table if the locale already existed.</returns>
215 + public VersionStringTable Add(int locale)
216 + {
217 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
218 + if (svi == null)
219 + {
220 + svi = new VersionInfo("StringFileInfo");
221 + this.rawVersionInfo.Add(svi);
222 + }
223 + foreach (VersionInfo strings in svi)
224 + {
225 + int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
226 + if (stringsLocale == locale)
227 + {
228 + return new VersionStringTable(this, strings);
229 + }
230 + }
231 +
232 + VersionInfo newStrings = new VersionInfo(
233 + ((ushort) locale).ToString("x4", CultureInfo.InvariantCulture) + ((ushort) 1200).ToString("x4", CultureInfo.InvariantCulture));
234 + svi.Add(newStrings);
235 + this.dirty = true;
236 +
237 + VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
238 + if (vvi == null)
239 + {
240 + vvi = new VersionInfo("VarFileInfo");
241 + vvi.Add(new VersionInfo("Translation"));
242 + this.rawVersionInfo.Add(vvi);
243 + }
244 + VersionInfo tVerInfo = vvi["Translation"];
245 + if (tVerInfo != null)
246 + {
247 + byte[] oldValue = tVerInfo.Data;
248 + if (oldValue == null) oldValue = new byte[0];
249 + tVerInfo.Data = new byte[oldValue.Length + 4];
250 + Array.Copy(oldValue, tVerInfo.Data, oldValue.Length);
251 + using (BinaryWriter bw = new BinaryWriter(new MemoryStream(tVerInfo.Data, oldValue.Length, 4, true)))
252 + {
253 + bw.Write((ushort) locale);
254 + bw.Write((ushort) 1200);
255 + }
256 + }
257 +
258 + return new VersionStringTable(this, newStrings);
259 + }
260 +
261 + /// <summary>
262 + /// Removes a version string table for a locale.
263 + /// </summary>
264 + /// <param name="locale">Locale of the table</param>
265 + public void Remove(int locale)
266 + {
267 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
268 + if (svi != null)
269 + {
270 + foreach (VersionInfo strings in svi)
271 + {
272 + int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
273 + if (stringsLocale == locale)
274 + {
275 + svi.Remove(strings);
276 + this.dirty = true;
277 + break;
278 + }
279 + }
280 +
281 + }
282 +
283 + VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
284 + if (vvi != null)
285 + {
286 + VersionInfo tVerInfo = vvi["Translation"];
287 + if (tVerInfo != null)
288 + {
289 + byte[] newValue = new byte[tVerInfo.Data.Length];
290 + int j = 0;
291 + using (BinaryWriter bw = new BinaryWriter(new MemoryStream(newValue, 0, newValue.Length, true)))
292 + {
293 + using (BinaryReader br = new BinaryReader(new MemoryStream(tVerInfo.Data)))
294 + {
295 + for (int i = tVerInfo.Data.Length / 4; i > 0; i--)
296 + {
297 + ushort tLocale = br.ReadUInt16();
298 + ushort cp = br.ReadUInt16();
299 + if (tLocale != locale)
300 + {
301 + bw.Write((ushort) tLocale);
302 + bw.Write((ushort) cp);
303 + j++;
304 + }
305 + }
306 + }
307 + }
308 + tVerInfo.Data = new byte[j * 4];
309 + Array.Copy(newValue, tVerInfo.Data, tVerInfo.Data.Length);
310 + }
311 + }
312 + }
313 +
314 + /// <summary>
315 + /// Checks if a version string table exists for a given locale.
316 + /// </summary>
317 + /// <param name="locale">Locale to search for</param>
318 + /// <returns>True if a string table was found for the locale; false otherwise.</returns>
319 + public bool Contains(int locale)
320 + {
321 + return this[locale] != null;
322 + }
323 +
324 + /// <summary>
325 + /// Gets the number string tables in the version resource.
326 + /// </summary>
327 + public int Count
328 + {
329 + get
330 + {
331 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
332 + return svi != null ? svi.Count : 0;
333 + }
334 + }
335 +
336 + /// <summary>
337 + /// Removes all string tables from the version resource.
338 + /// </summary>
339 + public void Clear()
340 + {
341 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
342 + if (svi != null)
343 + {
344 + svi.Clear();
345 + this.dirty = true;
346 + }
347 + }
348 +
349 + bool ICollection<VersionStringTable>.IsReadOnly
350 + {
351 + get
352 + {
353 + return false;
354 + }
355 + }
356 +
357 + void ICollection<VersionStringTable>.Add(VersionStringTable item)
358 + {
359 + throw new NotSupportedException();
360 + }
361 +
362 + bool ICollection<VersionStringTable>.Remove(VersionStringTable item)
363 + {
364 + throw new NotSupportedException();
365 + }
366 +
367 + bool ICollection<VersionStringTable>.Contains(VersionStringTable item)
368 + {
369 + throw new NotSupportedException();
370 + }
371 +
372 + /// <summary>
373 + /// Copies the version string tables to an array, starting at a particular array index.
374 + /// </summary>
375 + /// <param name="array">The one-dimensional Array that is the destination of the elements copied
376 + /// from the collection. The Array must have zero-based indexing.</param>
377 + /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
378 + public void CopyTo(VersionStringTable[] array, int arrayIndex)
379 + {
380 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
381 + if (svi != null)
382 + {
383 + foreach (VersionInfo strings in svi)
384 + {
385 + array[arrayIndex++] = new VersionStringTable(this, strings);
386 + }
387 + }
388 + }
389 +
390 + /// <summary>
391 + /// Gets an enumerator that can iterate over the version string tables in the collection.
392 + /// </summary>
393 + /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
394 + public IEnumerator<VersionStringTable> GetEnumerator()
395 + {
396 + VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
397 + if (svi != null)
398 + {
399 + foreach (VersionInfo strings in svi)
400 + {
401 + yield return new VersionStringTable(this, strings);
402 + }
403 + }
404 + }
405 +
406 + /// <summary>
407 + /// Gets an enumerator that can iterate over the version string tables in the collection.
408 + /// </summary>
409 + /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
410 + IEnumerator IEnumerable.GetEnumerator()
411 + {
412 + return this.GetEnumerator();
413 + }
414 + }
415 +}
src/dtf/WixToolset.Dtf.Resources/VersionStringTable.cs new
+231
@@ -0,0 +1,231 @@
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 +namespace WixToolset.Dtf.Resources
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Collections;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// Represents a string table of a file version resource.
16 + /// </summary>
17 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
18 + public sealed class VersionStringTable : IDictionary<string, string>
19 + {
20 + private VersionResource parent;
21 + private VersionInfo rawStringVersionInfo;
22 +
23 + internal VersionStringTable(VersionResource parent, VersionInfo rawStringVersionInfo)
24 + {
25 + this.parent = parent;
26 + this.rawStringVersionInfo = rawStringVersionInfo;
27 + }
28 +
29 + /// <summary>
30 + /// Gets the locale (LCID) of the string table.
31 + /// </summary>
32 + public int Locale
33 + {
34 + get
35 + {
36 + return UInt16.Parse(rawStringVersionInfo.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
37 + }
38 + set
39 + {
40 + rawStringVersionInfo.Key = ((ushort) value).ToString("x4", CultureInfo.InvariantCulture) + rawStringVersionInfo.Key.Substring(4, 4);
41 + this.parent.dirty = true;
42 + }
43 + }
44 +
45 + /// <summary>
46 + /// Gets or sets a string value.
47 + /// </summary>
48 + /// <param name="key">Name of the string.</param>
49 + public string this[string key]
50 + {
51 + get
52 + {
53 + VersionInfo verValue = this.rawStringVersionInfo[key];
54 + if (verValue == null)
55 + {
56 + return null;
57 + }
58 + else
59 + {
60 + return Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2);
61 + }
62 + }
63 + set
64 + {
65 + if (value == null)
66 + {
67 + rawStringVersionInfo.Remove(key);
68 + }
69 + else
70 + {
71 + VersionInfo verValue = rawStringVersionInfo[key];
72 + if (verValue == null)
73 + {
74 + verValue = new VersionInfo(key);
75 + verValue.IsString = true;
76 + rawStringVersionInfo.Add(verValue);
77 + }
78 + verValue.Data = new byte[Encoding.Unicode.GetByteCount(value) + 2];
79 + Encoding.Unicode.GetBytes(value, 0, value.Length, verValue.Data, 0);
80 + }
81 + this.parent.dirty = true;
82 + }
83 + }
84 +
85 + bool ICollection<KeyValuePair<string, string>>.IsReadOnly
86 + {
87 + get
88 + {
89 + return false;
90 + }
91 + }
92 +
93 + bool IDictionary<string, string>.TryGetValue(string key, out string value)
94 + {
95 + value = this[key];
96 + return value != null;
97 + }
98 +
99 +
100 + void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)
101 + {
102 + this[item.Key] = item.Value;
103 + }
104 +
105 + bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
106 + {
107 + string value = this[item.Key];
108 + if (value == item.Value)
109 + {
110 + this[item.Key] = null;
111 + return true;
112 + }
113 + else
114 + {
115 + return false;
116 + }
117 + }
118 +
119 + bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
120 + {
121 + string value = this[item.Key];
122 + if (value == item.Value)
123 + {
124 + return true;
125 + }
126 + else
127 + {
128 + return false;
129 + }
130 + }
131 +
132 + bool IDictionary<string, string>.ContainsKey(string key)
133 + {
134 + return this[key] != null;
135 + }
136 +
137 + void IDictionary<string, string>.Add(string key, string value)
138 + {
139 + this[key] = value;
140 + }
141 +
142 + bool IDictionary<string, string>.Remove(string key)
143 + {
144 + if (this[key] != null)
145 + {
146 + this[key] = null;
147 + return true;
148 + }
149 + else
150 + {
151 + return false;
152 + }
153 + }
154 +
155 + /// <summary>
156 + /// Removes all strings from the string table.
157 + /// </summary>
158 + public void Clear()
159 + {
160 + this.rawStringVersionInfo.Clear();
161 + }
162 +
163 + /// <summary>
164 + /// Gets a collection of all the names of the strings in the table.
165 + /// </summary>
166 + public ICollection<string> Keys
167 + {
168 + get
169 + {
170 + List<string> keys = new List<string>(this.rawStringVersionInfo.Count);
171 + foreach (VersionInfo verValue in this.rawStringVersionInfo)
172 + {
173 + keys.Add(verValue.Key);
174 + }
175 + return keys;
176 + }
177 + }
178 +
179 + /// <summary>
180 + /// Gets a collection of all the values in the table.
181 + /// </summary>
182 + public ICollection<string> Values
183 + {
184 + get
185 + {
186 + List<string> values = new List<string>(this.rawStringVersionInfo.Count);
187 + foreach (VersionInfo verValue in this.rawStringVersionInfo)
188 + {
189 + values.Add(Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
190 + }
191 + return values;
192 + }
193 + }
194 +
195 + /// <summary>
196 + /// Gets the number of strings in the table.
197 + /// </summary>
198 + public int Count
199 + {
200 + get
201 + {
202 + return this.rawStringVersionInfo.Count;
203 + }
204 + }
205 +
206 + void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int index)
207 + {
208 + foreach (VersionInfo verValue in this.rawStringVersionInfo)
209 + {
210 + array[index++] = new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
211 + }
212 + }
213 +
214 + /// <summary>
215 + /// Gets an enumeration over all strings in the table.
216 + /// </summary>
217 + /// <returns>Enumeration of string name and value pairs</returns>
218 + public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
219 + {
220 + foreach (VersionInfo verValue in this.rawStringVersionInfo)
221 + {
222 + yield return new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
223 + }
224 + }
225 +
226 + IEnumerator IEnumerable.GetEnumerator()
227 + {
228 + return this.GetEnumerator();
229 + }
230 + }
231 +}
src/dtf/WixToolset.Dtf.Resources/WixToolset.Dtf.Resources.csproj new
+17
@@ -0,0 +1,17 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +<Project Sdk="Microsoft.NET.Sdk">
5 + <PropertyGroup>
6 + <RootNamespace>WixToolset.Dtf.Resources</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.Resources</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Classes for reading and writing resource data in executable files</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
15 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
16 + </ItemGroup>
17 +</Project>
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/AssemblyInfo.cs new
+6
@@ -0,0 +1,6 @@
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 +using System.Diagnostics.CodeAnalysis;
4 +
5 +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "WixToolset.Dtf.WindowsInstaller.Linq.QTable`1.System.Linq.IQueryable<TRecord>.CreateQuery(System.Linq.Expressions.Expression):System.Linq.IQueryable`1<TElement>")]
6 +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "WixToolset.Dtf.WindowsInstaller.Linq.QTable`1.System.Linq.IQueryable<TRecord>.Execute(System.Linq.Expressions.Expression):TResult")]
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Attributes.cs new
+60
@@ -0,0 +1,60 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq
4 +{
5 + using System;
6 +
7 + /// <summary>
8 + /// Apply to a subclass of QRecord to indicate the name of
9 + /// the table the record type is to be used with.
10 + /// </summary>
11 + /// <remarks>
12 + /// If this attribute is not used on a record type, the default
13 + /// table name will be derived from the record type name. (An
14 + /// optional underscore suffix is stripped.)
15 + /// </remarks>
16 + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
17 + public class DatabaseTableAttribute : Attribute
18 + {
19 + /// <summary>
20 + /// Creates a new DatabaseTableAttribute for the specified table.
21 + /// </summary>
22 + /// <param name="table">name of the table associated with the record type</param>
23 + public DatabaseTableAttribute(string table)
24 + {
25 + this.Table = table;
26 + }
27 +
28 + /// <summary>
29 + /// Gets or sets the table associated with the record type.
30 + /// </summary>
31 + public string Table { get; set; }
32 + }
33 +
34 + /// <summary>
35 + /// Apply to a property on a subclass of QRecord to indicate
36 + /// the name of the column the property is to be associated with.
37 + /// </summary>
38 + /// <remarks>
39 + /// If this attribute is not used on a property, the default
40 + /// column name will be the same as the property name.
41 + /// </remarks>
42 + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
43 + public class DatabaseColumnAttribute : Attribute
44 + {
45 + /// <summary>
46 + /// Creates a new DatabaseColumnAttribute which maps a
47 + /// record property to a column.
48 + /// </summary>
49 + /// <param name="column">name of the column associated with the property</param>
50 + public DatabaseColumnAttribute(string column)
51 + {
52 + this.Column = column;
53 + }
54 +
55 + /// <summary>
56 + /// Gets or sets the column associated with the record property.
57 + /// </summary>
58 + public string Column { get; set; }
59 + }
60 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Entities.cs new
+150
@@ -0,0 +1,150 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq.Entities
4 +{
5 + // Silence warnings about style and doc-comments
6 + #if !CODE_ANALYSIS
7 + #pragma warning disable 1591
8 + #region Generated code
9 +
10 + public class Component_ : QRecord
11 + {
12 + public string Component { get { return this[0]; } set { this[0] = value; } }
13 + public string ComponentId { get { return this[1]; } set { this[1] = value; } }
14 + public string Directory_ { get { return this[2]; } set { this[2] = value; } }
15 + public string Condition { get { return this[4]; } set { this[4] = value; } }
16 + public string KeyPath { get { return this[5]; } set { this[5] = value; } }
17 + public ComponentAttributes Attributes
18 + { get { return (ComponentAttributes) this.I(3); } set { this[3] = ((int) value).ToString(); } }
19 + }
20 +
21 + public class CreateFolder_ : QRecord
22 + {
23 + public string Directory_ { get { return this[0]; } set { this[0] = value; } }
24 + public string Component_ { get { return this[1]; } set { this[1] = value; } }
25 + }
26 +
27 + public class CustomAction_ : QRecord
28 + {
29 + public string Action { get { return this[0]; } set { this[0] = value; } }
30 + public string Source { get { return this[2]; } set { this[2] = value; } }
31 + public string Target { get { return this[3]; } set { this[3] = value; } }
32 + public CustomActionTypes Type
33 + { get { return (CustomActionTypes) this.I(1); } set { this[1] = ((int) value).ToString(); } }
34 + }
35 +
36 + public class Directory_ : QRecord
37 + {
38 + public string Directory { get { return this[0]; } set { this[0] = value; } }
39 + public string Directory_Parent { get { return this[1]; } set { this[1] = value; } }
40 + public string DefaultDir { get { return this[2]; } set { this[2] = value; } }
41 + }
42 +
43 + public class DuplicateFile_ : QRecord
44 + {
45 + public string FileKey { get { return this[0]; } set { this[0] = value; } }
46 + public string Component_ { get { return this[1]; } set { this[1] = value; } }
47 + public string File_ { get { return this[2]; } set { this[2] = value; } }
48 + public string DestName { get { return this[4]; } set { this[4] = value; } }
49 + public string DestFolder { get { return this[5]; } set { this[5] = value; } }
50 + }
51 +
52 + public class Feature_ : QRecord
53 + {
54 + public string Feature { get { return this[0]; } set { this[0] = value; } }
55 + public string Feature_Parent { get { return this[1]; } set { this[1] = value; } }
56 + public string Title { get { return this[2]; } set { this[2] = value; } }
57 + public string Description { get { return this[3]; } set { this[3] = value; } }
58 + public int? Display { get { return this.NI(4); } set { this[4] = value.ToString(); } }
59 + public int Level { get { return this.I(5); } set { this[5] = value.ToString(); } }
60 + public string Directory_ { get { return this[6]; } set { this[6] = value; } }
61 + public FeatureAttributes Attributes
62 + { get { return (FeatureAttributes) this.I(7); } set { this[7] = ((int) value).ToString(); } }
63 + }
64 +
65 + [DatabaseTable("FeatureComponents")]
66 + public class FeatureComponent_ : QRecord
67 + {
68 + public string Feature_ { get { return this[0]; } set { this[0] = value; } }
69 + public string Component_ { get { return this[1]; } set { this[1] = value; } }
70 + }
71 +
72 + public class File_ : QRecord
73 + {
74 + public string File { get { return this[0]; } set { this[0] = value; } }
75 + public string Component_ { get { return this[1]; } set { this[1] = value; } }
76 + public string FileName { get { return this[2]; } set { this[2] = value; } }
77 + public int FileSize { get { return this.I(3); } set { this[3] = value.ToString(); } }
78 + public string Version { get { return this[4]; } set { this[4] = value; } }
79 + public string Language { get { return this[5]; } set { this[5] = value; } }
80 + public int Sequence { get { return this.I(7); } set { this[7] = value.ToString(); } }
81 + public FileAttributes Attributes
82 + { get { return (FileAttributes) this.I(6); } set { this[6] = ((int) value).ToString(); } }
83 + }
84 +
85 + [DatabaseTable("MsiFileHash")]
86 + public class FileHash_ : QRecord
87 + {
88 + public string File_ { get { return this[0]; } set { this[0] = value; } }
89 + public int Options { get { return this.I(1); } set { this[1] = value.ToString(); } }
90 + public int HashPart1 { get { return this.I(2); } set { this[2] = value.ToString(); } }
91 + public int HashPart2 { get { return this.I(3); } set { this[3] = value.ToString(); } }
92 + public int HashPart3 { get { return this.I(4); } set { this[4] = value.ToString(); } }
93 + public int HashPart4 { get { return this.I(5); } set { this[5] = value.ToString(); } }
94 + }
95 +
96 + [DatabaseTable("InstallExecuteSequence")]
97 + public class InstallSequence_ : QRecord
98 + {
99 + public string Action { get { return this[0]; } set { this[0] = value; } }
100 + public string Condition { get { return this[1]; } set { this[1] = value; } }
101 + public int Sequence { get { return this.I(2); } set { this[2] = value.ToString(); } }
102 + }
103 +
104 + public class LaunchCondition_ : QRecord
105 + {
106 + public string Condition { get { return this[0]; } set { this[0] = value; } }
107 + public string Description { get { return this[1]; } set { this[1] = value; } }
108 + }
109 +
110 + public class Media_ : QRecord
111 + {
112 + public int DiskId { get { return this.I(0); } set { this[0] = value.ToString(); } }
113 + public int LastSequence { get { return this.I(1); } set { this[1] = value.ToString(); } }
114 + public string DiskPrompt { get { return this[2]; } set { this[2] = value; } }
115 + public string Cabinet { get { return this[3]; } set { this[3] = value; } }
116 + public string VolumeLabel { get { return this[4]; } set { this[4] = value; } }
117 + public string Source { get { return this[5]; } set { this[5] = value; } }
118 + }
119 +
120 + public class Property_ : QRecord
121 + {
122 + public string Property { get { return this[0]; } set { this[0] = value; } }
123 + public string Value { get { return this[1]; } set { this[1] = value; } }
124 + }
125 +
126 + public class Registry_ : QRecord
127 + {
128 + public string Registry { get { return this[0]; } set { this[0] = value; } }
129 + public string Key { get { return this[2]; } set { this[2] = value; } }
130 + public string Name { get { return this[3]; } set { this[3] = value; } }
131 + public string Value { get { return this[4]; } set { this[4] = value; } }
132 + public string Component_ { get { return this[5]; } set { this[5] = value; } }
133 + public RegistryRoot Root
134 + { get { return (RegistryRoot) this.I(1); } set { this[0] = ((int) value).ToString(); } }
135 + }
136 +
137 + public class RemoveFile_ : QRecord
138 + {
139 + public string FileKey { get { return this[0]; } set { this[0] = value; } }
140 + public string Component_ { get { return this[2]; } set { this[2] = value; } }
141 + public string FileName { get { return this[3]; } set { this[3] = value; } }
142 + public string DirProperty { get { return this[4]; } set { this[4] = value; } }
143 + public RemoveFileModes InstallMode
144 + { get { return (RemoveFileModes) this.I(5); } set { this[5] = ((int) value).ToString(); } }
145 + }
146 +
147 + #endregion // Generated code
148 + #pragma warning restore 1591
149 + #endif // !CODE_ANALYSIS
150 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QDatabase.cs new
+214
@@ -0,0 +1,214 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq
4 +{
5 + using System;
6 + using System.IO;
7 + using WixToolset.Dtf.WindowsInstaller.Linq.Entities;
8 +
9 + /// <summary>
10 + /// Allows any Database instance to be converted into a queryable database.
11 + /// </summary>
12 + public static class Queryable
13 + {
14 + /// <summary>
15 + /// Converts any Database instance into a queryable database.
16 + /// </summary>
17 + /// <param name="db"></param>
18 + /// <returns>Queryable database instance that operates on the same
19 + /// MSI handle.</returns>
20 + /// <remarks>
21 + /// This extension method is meant for convenient on-the-fly conversion.
22 + /// If the existing database instance already happens to be a QDatabase,
23 + /// then it is returned unchanged. Otherwise since the new database
24 + /// carries the same MSI handle, only one of the instances needs to be
25 + /// closed, not both.
26 + /// </remarks>
27 + public static QDatabase AsQueryable(this Database db)
28 + {
29 + QDatabase qdb = db as QDatabase;
30 + if (qdb == null && db != null)
31 + {
32 + qdb = new QDatabase(db.Handle, true, db.FilePath, db.OpenMode);
33 + }
34 + return qdb;
35 + }
36 + }
37 +
38 + /// <summary>
39 + /// Queryable MSI database - extends the base Database class with
40 + /// LINQ query functionality along with predefined entity types
41 + /// for common tables.
42 + /// </summary>
43 + public class QDatabase : Database
44 + {
45 + /// <summary>
46 + /// Opens an existing database in read-only mode.
47 + /// </summary>
48 + /// <param name="filePath">Path to the database file.</param>
49 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
50 + /// <remarks>
51 + /// Because this constructor initiates database access, it cannot be used with a
52 + /// running installation.
53 + /// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
54 + /// The finalizer will close the handle if it is still open, however due to the nondeterministic
55 + /// nature of finalization it is best that the handle be closed manually as soon as it is no
56 + /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
57 + /// </remarks>
58 + public QDatabase(string filePath)
59 + : base(filePath)
60 + {
61 + }
62 +
63 + /// <summary>
64 + /// Opens an existing database with another database as output.
65 + /// </summary>
66 + /// <param name="filePath">Path to the database to be read.</param>
67 + /// <param name="outputPath">Open mode for the database</param>
68 + /// <returns>Database object representing the created or opened database</returns>
69 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
70 + /// <remarks>
71 + /// When a database is opened as the output of another database, the summary information stream
72 + /// of the output database is actually a read-only mirror of the original database and thus cannot
73 + /// be changed. Additionally, it is not persisted with the database. To create or modify the
74 + /// summary information for the output database it must be closed and re-opened.
75 + /// <para>The returned Database object should be <see cref="InstallerHandle.Close"/>d after use.
76 + /// The finalizer will close the handle if it is still open, however due to the nondeterministic
77 + /// nature of finalization it is best that the handle be closed manually as soon as it is no
78 + /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
79 + /// </remarks>
80 + public QDatabase(string filePath, string outputPath)
81 + : base(filePath, outputPath)
82 + {
83 + }
84 +
85 + /// <summary>
86 + /// Opens an existing database or creates a new one.
87 + /// </summary>
88 + /// <param name="filePath">Path to the database file. If an empty string
89 + /// is supplied, a temporary database is created that is not persisted.</param>
90 + /// <param name="mode">Open mode for the database</param>
91 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
92 + /// <remarks>
93 + /// To make and save changes to a database first open the database in transaction,
94 + /// create or, or direct mode. After making the changes, always call the Commit method
95 + /// before closing the database handle. The Commit method flushes all buffers.
96 + /// <para>Always call the Commit method on a database that has been opened in direct
97 + /// mode before closing the database. Failure to do this may corrupt the database.</para>
98 + /// <para>Because this constructor initiates database access, it cannot be used with a
99 + /// running installation.</para>
100 + /// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
101 + /// The finalizer will close the handle if it is still open, however due to the nondeterministic
102 + /// nature of finalization it is best that the handle be closed manually as soon as it is no
103 + /// longer needed, as leaving lots of unused handles open can degrade performance.</para>
104 + /// </remarks>
105 + public QDatabase(string filePath, DatabaseOpenMode mode)
106 + : base(filePath, mode)
107 + {
108 + }
109 +
110 + /// <summary>
111 + /// Creates a new database from an MSI handle.
112 + /// </summary>
113 + /// <param name="handle">Native MSI database handle.</param>
114 + /// <param name="ownsHandle">True if the handle should be closed
115 + /// when the database object is disposed</param>
116 + /// <param name="filePath">Path of the database file, if known</param>
117 + /// <param name="openMode">Mode the handle was originally opened in</param>
118 + protected internal QDatabase(
119 + IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode)
120 + : base(handle, ownsHandle, filePath, openMode)
121 + {
122 + }
123 +
124 + /// <summary>
125 + /// Gets or sets a log where all MSI SQL queries are written.
126 + /// </summary>
127 + /// <remarks>
128 + /// The log can be useful for debugging, or simply to watch the LINQ magic in action.
129 + /// </remarks>
130 + public TextWriter Log { get; set; }
131 +
132 + /// <summary>
133 + /// Gets a queryable table from the datbaase.
134 + /// </summary>
135 + /// <param name="table">name of the table</param>
136 + public QTable<QRecord> this[string table]
137 + {
138 + get
139 + {
140 + return new QTable<QRecord>(this, table);
141 + }
142 + }
143 +
144 + #if !CODE_ANALYSIS
145 + #region Queryable tables
146 +
147 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
148 + public QTable<Component_> Components
149 + { get { return new QTable<Component_>(this); } }
150 +
151 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
152 + public QTable<CreateFolder_> CreateFolders
153 + { get { return new QTable<CreateFolder_>(this); } }
154 +
155 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
156 + public QTable<CustomAction_> CustomActions
157 + { get { return new QTable<CustomAction_>(this); } }
158 +
159 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
160 + public QTable<Directory_> Directories
161 + { get { return new QTable<Directory_>(this); } }
162 +
163 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
164 + public QTable<DuplicateFile_> DuplicateFiles
165 + { get { return new QTable<DuplicateFile_>(this); } }
166 +
167 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
168 + public QTable<Feature_> Features
169 + { get { return new QTable<Feature_>(this); } }
170 +
171 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
172 + public QTable<FeatureComponent_> FeatureComponents
173 + { get { return new QTable<FeatureComponent_>(this); } }
174 +
175 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
176 + public QTable<File_> Files
177 + { get { return new QTable<File_>(this); } }
178 +
179 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
180 + public QTable<FileHash_> FileHashes
181 + { get { return new QTable<FileHash_>(this); } }
182 +
183 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
184 + public QTable<InstallSequence_> InstallExecuteSequences
185 + { get { return new QTable<InstallSequence_>(this, "InstallExecuteSequence"); } }
186 +
187 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
188 + public QTable<InstallSequence_> InstallUISequences
189 + { get { return new QTable<InstallSequence_>(this, "InstallUISequence"); } }
190 +
191 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
192 + public QTable<LaunchCondition_> LaunchConditions
193 + { get { return new QTable<LaunchCondition_>(this); } }
194 +
195 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
196 + public QTable<Media_> Medias
197 + { get { return new QTable<Media_>(this); } }
198 +
199 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
200 + public QTable<Property_> Properties
201 + { get { return new QTable<Property_>(this); } }
202 +
203 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
204 + public QTable<Registry_> Registries
205 + { get { return new QTable<Registry_>(this); } }
206 +
207 + /// <summary>Queryable standard table with predefined specialized record type.</summary>
208 + public QTable<RemoveFile_> RemoveFiles
209 + { get { return new QTable<RemoveFile_>(this); } }
210 +
211 + #endregion // Queryable tables
212 + #endif // !CODE_ANALYSIS
213 + }
214 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs new
+501
@@ -0,0 +1,501 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Globalization;
9 + using System.Collections.Generic;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + /// <summary>
13 + /// Generic record entity for queryable databases,
14 + /// and base for strongly-typed entity subclasses.
15 + /// </summary>
16 + /// <remarks>
17 + /// Several predefined specialized subclasses are provided for common
18 + /// standard tables. Subclasses for additional standard tables
19 + /// or custom tables are not necessary, but they are easy to create
20 + /// and make the coding experience much nicer.
21 + /// <para>When creating subclasses, the following attributes may be
22 + /// useful: <see cref="DatabaseTableAttribute"/>,
23 + /// <see cref="DatabaseColumnAttribute"/></para>
24 + /// </remarks>
25 + public class QRecord
26 + {
27 + /// <summary>
28 + /// Do not call. Use QTable.NewRecord() instead.
29 + /// </summary>
30 + /// <remarks>
31 + /// Subclasses must also provide a public parameterless constructor.
32 + /// <para>QRecord constructors are only public due to implementation
33 + /// reasons (to satisfy the new() constraint on the QTable generic
34 + /// class). They are not intended to be called by user code other than
35 + /// a subclass constructor. If the constructor is invoked directly,
36 + /// the record instance will not be properly initialized (associated
37 + /// with a database table) and calls to methods on the instance
38 + /// will throw a NullReferenceException.</para>
39 + /// </remarks>
40 + /// <seealso cref="QTable&lt;TRecord&gt;.NewRecord()"/>
41 + public QRecord()
42 + {
43 + }
44 +
45 + internal QDatabase Database { get; set; }
46 +
47 + internal TableInfo TableInfo { get; set; }
48 +
49 + internal IList<string> Values { get; set; }
50 +
51 + internal bool Exists { get; set; }
52 +
53 + /// <summary>
54 + /// Gets the number of fields in the record.
55 + /// </summary>
56 + public int FieldCount
57 + {
58 + get
59 + {
60 + return this.Values.Count;
61 + }
62 + }
63 +
64 + /// <summary>
65 + /// Gets or sets a record field.
66 + /// </summary>
67 + /// <param name="field">column name of the field</param>
68 + /// <remarks>
69 + /// Setting a field value will automatically update the database.
70 + /// </remarks>
71 + public string this[string field]
72 + {
73 + get
74 + {
75 + if (field == null)
76 + {
77 + throw new ArgumentNullException("field");
78 + }
79 +
80 + int index = this.TableInfo.Columns.IndexOf(field);
81 + if (index < 0)
82 + {
83 + throw new ArgumentOutOfRangeException("field");
84 + }
85 +
86 + return this[index];
87 + }
88 +
89 + set
90 + {
91 + if (field == null)
92 + {
93 + throw new ArgumentNullException("field");
94 + }
95 +
96 + this.Update(new string[] { field }, new string[] { value });
97 + }
98 + }
99 +
100 + /// <summary>
101 + /// Gets or sets a record field.
102 + /// </summary>
103 + /// <param name="index">zero-based column index of the field</param>
104 + /// <remarks>
105 + /// Setting a field value will automatically update the database.
106 + /// </remarks>
107 + public string this[int index]
108 + {
109 + get
110 + {
111 + if (index < 0 || index >= this.FieldCount)
112 + {
113 + throw new ArgumentOutOfRangeException("index");
114 + }
115 +
116 + return this.Values[index];
117 + }
118 +
119 + set
120 + {
121 + if (index < 0 || index >= this.FieldCount)
122 + {
123 + throw new ArgumentOutOfRangeException("index");
124 + }
125 +
126 + this.Update(new int[] { index }, new string[] { value });
127 + }
128 + }
129 +
130 + /// <summary>
131 + /// Used by subclasses to get a field as an integer.
132 + /// </summary>
133 + /// <param name="index">zero-based column index of the field</param>
134 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")]
135 + protected int I(int index)
136 + {
137 + string value = this[index];
138 + return value.Length > 0 ?
139 + Int32.Parse(value, CultureInfo.InvariantCulture) : 0;
140 + }
141 +
142 + /// <summary>
143 + /// Used by subclasses to get a field as a nullable integer.
144 + /// </summary>
145 + /// <param name="index">zero-based column index of the field</param>
146 + protected int? NI(int index)
147 + {
148 + string value = this[index];
149 + return value.Length > 0 ?
150 + new int?(Int32.Parse(value, CultureInfo.InvariantCulture)) : null;
151 + }
152 +
153 + /// <summary>
154 + /// Dumps all record fields to a string.
155 + /// </summary>
156 + public override string ToString()
157 + {
158 + StringBuilder buf = new StringBuilder(this.GetType().Name);
159 + buf.Append(" {");
160 + for (int i = 0; i < this.FieldCount; i++)
161 + {
162 + buf.AppendFormat("{0} {1} = {2}",
163 + (i > 0 ? "," : String.Empty),
164 + this.TableInfo.Columns[i].Name,
165 + this[i]);
166 + }
167 + buf.Append(" }");
168 + return buf.ToString();
169 + }
170 +
171 + /// <summary>
172 + /// Update multiple fields in the record (and the database).
173 + /// </summary>
174 + /// <param name="fields">column names of fields to update</param>
175 + /// <param name="values">new values for each field being updated</param>
176 + public void Update(IList<string> fields, IList<string> values)
177 + {
178 + if (fields == null)
179 + {
180 + throw new ArgumentNullException("fields");
181 + }
182 +
183 + if (values == null)
184 + {
185 + throw new ArgumentNullException("values");
186 + }
187 +
188 + if (fields.Count == 0 || values.Count == 0 ||
189 + fields.Count > this.FieldCount ||
190 + values.Count != fields.Count)
191 + {
192 + throw new ArgumentOutOfRangeException("fields");
193 + }
194 +
195 + int[] indexes = new int[fields.Count];
196 + for (int i = 0; i < indexes.Length; i++)
197 + {
198 + if (fields[i] == null)
199 + {
200 + throw new ArgumentNullException("fields[" + i + "]");
201 + }
202 +
203 + indexes[i] = this.TableInfo.Columns.IndexOf(fields[i]);
204 +
205 + if (indexes[i] < 0)
206 + {
207 + throw new ArgumentOutOfRangeException("fields[" + i + "]");
208 + }
209 + }
210 +
211 + this.Update(indexes, values);
212 + }
213 +
214 + /// <summary>
215 + /// Update multiple fields in the record (and the database).
216 + /// </summary>
217 + /// <param name="indexes">column indexes of fields to update</param>
218 + /// <param name="values">new values for each field being updated</param>
219 + /// <remarks>
220 + /// The record (primary keys) must already exist in the table.
221 + /// <para>Updating primary key fields is not yet implemented; use Delete()
222 + /// and Insert() instead.</para>
223 + /// </remarks>
224 + public void Update(IList<int> indexes, IList<string> values)
225 + {
226 + if (indexes == null)
227 + {
228 + throw new ArgumentNullException("indexes");
229 + }
230 +
231 + if (values == null)
232 + {
233 + throw new ArgumentNullException("values");
234 + }
235 +
236 + if (indexes.Count == 0 || values.Count == 0 ||
237 + indexes.Count > this.FieldCount ||
238 + values.Count != indexes.Count)
239 + {
240 + throw new ArgumentOutOfRangeException("indexes");
241 + }
242 +
243 + bool primaryKeyChanged = false;
244 + for (int i = 0; i < indexes.Count; i++)
245 + {
246 + int index = indexes[i];
247 + if (index < 0 || index >= this.FieldCount)
248 + {
249 + throw new ArgumentOutOfRangeException("index[" + i + "]");
250 + }
251 +
252 + ColumnInfo col = this.TableInfo.Columns[index];
253 + if (this.TableInfo.PrimaryKeys.Contains(col.Name))
254 + {
255 + if (values[i] == null)
256 + {
257 + throw new ArgumentNullException("values[" + i + "]");
258 + }
259 +
260 + primaryKeyChanged = true;
261 + }
262 + else if (values[i] == null)
263 + {
264 + if (col.IsRequired)
265 + {
266 + throw new ArgumentNullException("values[" + i + "]");
267 + }
268 + }
269 +
270 + this.Values[index] = values[i];
271 + }
272 +
273 + if (this.Exists)
274 + {
275 + if (!primaryKeyChanged)
276 + {
277 + int updateRecSize = indexes.Count + this.TableInfo.PrimaryKeys.Count;
278 + using (Record updateRec = this.Database.CreateRecord(updateRecSize))
279 + {
280 + StringBuilder s = new StringBuilder("UPDATE `");
281 + s.Append(this.TableInfo.Name);
282 + s.Append("` SET");
283 +
284 + for (int i = 0; i < indexes.Count; i++)
285 + {
286 + ColumnInfo col = this.TableInfo.Columns[indexes[i]];
287 + if (col.Type == typeof(Stream))
288 + {
289 + throw new NotSupportedException(
290 + "Cannot update stream columns via QRecord.");
291 + }
292 +
293 + int index = indexes[i];
294 + s.AppendFormat("{0} `{1}` = ?",
295 + (i > 0 ? "," : String.Empty),
296 + col.Name);
297 +
298 + if (values[i] != null)
299 + {
300 + updateRec[i + 1] = values[i];
301 + }
302 + }
303 +
304 + for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
305 + {
306 + string key = this.TableInfo.PrimaryKeys[i];
307 + s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
308 + int index = this.TableInfo.Columns.IndexOf(key);
309 + updateRec[indexes.Count + i + 1] = this.Values[index];
310 +
311 + }
312 +
313 + string updateSql = s.ToString();
314 + TextWriter log = this.Database.Log;
315 + if (log != null)
316 + {
317 + log.WriteLine();
318 + log.WriteLine(updateSql);
319 + for (int field = 1; field <= updateRecSize; field++)
320 + {
321 + log.WriteLine(" ? = " + updateRec.GetString(field));
322 + }
323 + }
324 +
325 + this.Database.Execute(updateSql, updateRec);
326 + }
327 + }
328 + else
329 + {
330 + throw new NotImplementedException(
331 + "Update() cannot handle changed primary keys yet.");
332 + // TODO:
333 + // query using old values
334 + // update values
335 + // View.Replace
336 + }
337 + }
338 + }
339 +
340 + /// <summary>
341 + /// Inserts the record in the database.
342 + /// </summary>
343 + /// <remarks>
344 + /// The record (primary keys) may not already exist in the table.
345 + /// <para>Use <see cref="QTable&lt;TRecord&gt;.NewRecord()"/> to get a new
346 + /// record. Prmary keys and all required fields
347 + /// must be filled in before insertion.</para>
348 + /// </remarks>
349 + public void Insert()
350 + {
351 + this.Insert(false);
352 + }
353 +
354 + /// <summary>
355 + /// Inserts the record into the table.
356 + /// </summary>
357 + /// <param name="temporary">true if the record is temporarily
358 + /// inserted, to be visible only as long as the database is open</param>
359 + /// <remarks>
360 + /// The record (primary keys) may not already exist in the table.
361 + /// <para>Use <see cref="QTable&lt;TRecord&gt;.NewRecord()"/> to get a new
362 + /// record. Prmary keys and all required fields
363 + /// must be filled in before insertion.</para>
364 + /// </remarks>
365 + public void Insert(bool temporary)
366 + {
367 + using (Record updateRec = this.Database.CreateRecord(this.FieldCount))
368 + {
369 + string insertSql = this.TableInfo.SqlInsertString;
370 + if (temporary)
371 + {
372 + insertSql += " TEMPORARY";
373 + }
374 +
375 + TextWriter log = this.Database.Log;
376 + if (log != null)
377 + {
378 + log.WriteLine();
379 + log.WriteLine(insertSql);
380 + }
381 +
382 + for (int index = 0; index < this.FieldCount; index++)
383 + {
384 + ColumnInfo col = this.TableInfo.Columns[index];
385 + if (col.Type == typeof(Stream))
386 + {
387 + throw new NotSupportedException(
388 + "Cannot insert stream columns via QRecord.");
389 + }
390 +
391 + if (this.Values[index] != null)
392 + {
393 + updateRec[index + 1] = this.Values[index];
394 + }
395 +
396 + if (log != null)
397 + {
398 + log.WriteLine(" ? = " + this.Values[index]);
399 + }
400 + }
401 +
402 + this.Database.Execute(insertSql, updateRec);
403 + this.Exists = true;
404 + }
405 + }
406 +
407 + /// <summary>
408 + /// Deletes the record from the table if it exists.
409 + /// </summary>
410 + public void Delete()
411 + {
412 + using (Record keyRec = this.Database.CreateRecord(this.TableInfo.PrimaryKeys.Count))
413 + {
414 + StringBuilder s = new StringBuilder("DELETE FROM `");
415 + s.Append(this.TableInfo.Name);
416 + s.Append("`");
417 + for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
418 + {
419 + string key = this.TableInfo.PrimaryKeys[i];
420 + s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
421 + int index = this.TableInfo.Columns.IndexOf(key);
422 + keyRec[i + 1] = this.Values[index];
423 + }
424 +
425 + string deleteSql = s.ToString();
426 +
427 + TextWriter log = this.Database.Log;
428 + if (log != null)
429 + {
430 + log.WriteLine();
431 + log.WriteLine(deleteSql);
432 +
433 + for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
434 + {
435 + log.WriteLine(" ? = " + keyRec.GetString(i + 1));
436 + }
437 + }
438 +
439 + this.Database.Execute(deleteSql, keyRec);
440 + this.Exists = false;
441 + }
442 + }
443 +
444 + /// <summary>
445 + /// Not yet implemented.
446 + /// </summary>
447 + public void Refresh()
448 + {
449 + throw new NotImplementedException();
450 + }
451 +
452 + /// <summary>
453 + /// Not yet implemented.
454 + /// </summary>
455 + public void Assign()
456 + {
457 + throw new NotImplementedException();
458 + }
459 +
460 + /// <summary>
461 + /// Not yet implemented.
462 + /// </summary>
463 + public bool Merge()
464 + {
465 + throw new NotImplementedException();
466 + }
467 +
468 + /// <summary>
469 + /// Not yet implemented.
470 + /// </summary>
471 + public ICollection<ValidationErrorInfo> Validate()
472 + {
473 + throw new NotImplementedException();
474 + }
475 +
476 + /// <summary>
477 + /// Not yet implemented.
478 + /// </summary>
479 + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
480 + public ICollection<ValidationErrorInfo> ValidateNew()
481 + {
482 + throw new NotImplementedException();
483 + }
484 +
485 + /// <summary>
486 + /// Not yet implemented.
487 + /// </summary>
488 + public ICollection<ValidationErrorInfo> ValidateFields()
489 + {
490 + throw new NotImplementedException();
491 + }
492 +
493 + /// <summary>
494 + /// Not yet implemented.
495 + /// </summary>
496 + public ICollection<ValidationErrorInfo> ValidateDelete()
497 + {
498 + throw new NotImplementedException();
499 + }
500 + }
501 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs new
+296
@@ -0,0 +1,296 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 + using System.Reflection;
10 + using System.Linq;
11 + using System.Linq.Expressions;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// Represents one table in a LINQ-queryable Database.
16 + /// </summary>
17 + /// <typeparam name="TRecord">type that represents one record in the table</typeparam>
18 + /// <remarks>
19 + /// This class is the primary gateway to all LINQ to MSI query functionality.
20 + /// <para>The TRecord generic parameter may be the general <see cref="QRecord" />
21 + /// class, or a specialized subclass of QRecord.</para>
22 + /// </remarks>
23 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
24 + public sealed class QTable<TRecord> : IOrderedQueryable<TRecord>, IQueryProvider
25 + where TRecord : QRecord, new()
26 + {
27 + private QDatabase db;
28 + private TableInfo tableInfo;
29 +
30 + /// <summary>
31 + /// Infers the name of the table this instance will be
32 + /// associated with.
33 + /// </summary>
34 + /// <returns>table name</returns>
35 + /// <remarks>
36 + /// The table name is retrieved from a DatabaseTableAttribute
37 + /// on the record type if it exists; otherwise the name is
38 + /// derived from the name of the record type itself.
39 + /// (An optional underscore suffix on the record type name is dropped.)
40 + /// </remarks>
41 + private static string InferTableName()
42 + {
43 + foreach (DatabaseTableAttribute attr in typeof(TRecord).GetCustomAttributes(
44 + typeof(DatabaseTableAttribute), false))
45 + {
46 + string tableName = attr.Table;
47 + if (!String.IsNullOrEmpty(tableName))
48 + {
49 + return tableName;
50 + }
51 + }
52 +
53 + string recordTypeName = typeof(TRecord).Name;
54 + if (recordTypeName[recordTypeName.Length - 1] == '_')
55 + {
56 + return recordTypeName.Substring(0, recordTypeName.Length - 1);
57 + }
58 + else
59 + {
60 + return recordTypeName;
61 + }
62 + }
63 +
64 + /// <summary>
65 + /// Creates a new QTable, inferring the table name
66 + /// from the name of the record type parameter.
67 + /// </summary>
68 + /// <param name="db">database that contains the table</param>
69 + public QTable(QDatabase db)
70 + : this(db, InferTableName())
71 + {
72 + }
73 +
74 + /// <summary>
75 + /// Creates a new QTable with an explicit table name.
76 + /// </summary>
77 + /// <param name="db">database that contains the table</param>
78 + /// <param name="table">name of the table</param>
79 + public QTable(QDatabase db, string table)
80 + {
81 + if (db == null)
82 + {
83 + throw new ArgumentNullException("db");
84 + }
85 +
86 + if (String.IsNullOrEmpty(table))
87 + {
88 + throw new ArgumentNullException("table");
89 + }
90 +
91 + this.db = db;
92 + this.tableInfo = db.Tables[table];
93 + if (this.tableInfo == null)
94 + {
95 + throw new ArgumentException(
96 + "Table does not exist in database: " + table);
97 + }
98 + }
99 +
100 + /// <summary>
101 + /// Gets schema information about the table.
102 + /// </summary>
103 + public TableInfo TableInfo
104 + {
105 + get
106 + {
107 + return this.tableInfo;
108 + }
109 + }
110 +
111 + /// <summary>
112 + /// Gets the database this table is associated with.
113 + /// </summary>
114 + public QDatabase Database
115 + {
116 + get
117 + {
118 + return this.db;
119 + }
120 + }
121 +
122 + /// <summary>
123 + /// Enumerates over all records in the table.
124 + /// </summary>
125 + /// <returns></returns>
126 + public IEnumerator<TRecord> GetEnumerator()
127 + {
128 + string query = this.tableInfo.SqlSelectString;
129 +
130 + TextWriter log = this.db.Log;
131 + if (log != null)
132 + {
133 + log.WriteLine();
134 + log.WriteLine(query);
135 + }
136 +
137 + using (View view = db.OpenView(query))
138 + {
139 + view.Execute();
140 +
141 + ColumnCollection columns = this.tableInfo.Columns;
142 + int columnCount = columns.Count;
143 + bool[] isBinary = new bool[columnCount];
144 +
145 + for (int i = 0; i < isBinary.Length; i++)
146 + {
147 + isBinary[i] = columns[i].Type == typeof(System.IO.Stream);
148 + }
149 +
150 + foreach (Record rec in view) using (rec)
151 + {
152 + string[] values = new string[columnCount];
153 + for (int i = 0; i < values.Length; i++)
154 + {
155 + values[i] = isBinary[i] ? "[Binary Data]" : rec.GetString(i + 1);
156 + }
157 +
158 + TRecord trec = new TRecord();
159 + trec.Database = this.Database;
160 + trec.TableInfo = this.TableInfo;
161 + trec.Values = values;
162 + trec.Exists = true;
163 + yield return trec;
164 + }
165 + }
166 + }
167 +
168 + IEnumerator IEnumerable.GetEnumerator()
169 + {
170 + return ((IEnumerable<TRecord>) this).GetEnumerator();
171 + }
172 +
173 + IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
174 + {
175 + if (expression == null)
176 + {
177 + throw new ArgumentNullException("expression");
178 + }
179 +
180 + Query<TElement> q = new Query<TElement>(this.Database, expression);
181 +
182 + MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
183 + string methodName = methodCallExpression.Method.Name;
184 + if (methodName == "Where")
185 + {
186 + LambdaExpression argumentExpression = (LambdaExpression)
187 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
188 + q.BuildQuery(this.TableInfo, argumentExpression);
189 + }
190 + else if (methodName == "OrderBy")
191 + {
192 + LambdaExpression argumentExpression = (LambdaExpression)
193 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
194 + q.BuildSequence(this.TableInfo, argumentExpression);
195 + }
196 + else if (methodName == "Select")
197 + {
198 + LambdaExpression argumentExpression = (LambdaExpression)
199 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
200 + q.BuildNullQuery(this.TableInfo, typeof(TRecord), argumentExpression);
201 + q.BuildProjection(null, argumentExpression);
202 + }
203 + else if (methodName == "Join")
204 + {
205 + ConstantExpression constantExpression = (ConstantExpression)
206 + methodCallExpression.Arguments[1];
207 + IQueryable inner = (IQueryable) constantExpression.Value;
208 + q.PerformJoin(
209 + this.TableInfo,
210 + typeof(TRecord),
211 + inner,
212 + GetJoinLambda(methodCallExpression.Arguments[2]),
213 + GetJoinLambda(methodCallExpression.Arguments[3]),
214 + GetJoinLambda(methodCallExpression.Arguments[4]));
215 + }
216 + else
217 + {
218 + throw new NotSupportedException(
219 + "Query operation not supported: " + methodName);
220 + }
221 +
222 + return q;
223 + }
224 +
225 + private static LambdaExpression GetJoinLambda(Expression expresion)
226 + {
227 + UnaryExpression unaryExpression = (UnaryExpression) expresion;
228 + return (LambdaExpression) unaryExpression.Operand;
229 + }
230 +
231 + IQueryable IQueryProvider.CreateQuery(Expression expression)
232 + {
233 + return ((IQueryProvider) this).CreateQuery<TRecord>(expression);
234 + }
235 +
236 + TResult IQueryProvider.Execute<TResult>(Expression expression)
237 + {
238 + throw new NotSupportedException(
239 + "Direct method calls not supported -- use AsEnumerable() instead.");
240 + }
241 +
242 + object IQueryProvider.Execute(Expression expression)
243 + {
244 + throw new NotSupportedException(
245 + "Direct method calls not supported -- use AsEnumerable() instead.");
246 + }
247 +
248 + IQueryProvider IQueryable.Provider
249 + {
250 + get
251 + {
252 + return this;
253 + }
254 + }
255 +
256 + Type IQueryable.ElementType
257 + {
258 + get
259 + {
260 + return typeof(TRecord);
261 + }
262 + }
263 +
264 + Expression IQueryable.Expression
265 + {
266 + get
267 + {
268 + return Expression.Constant(this);
269 + }
270 + }
271 +
272 + /// <summary>
273 + /// Creates a new record that can be inserted into this table.
274 + /// </summary>
275 + /// <returns>a record with all fields initialized to null</returns>
276 + /// <remarks>
277 + /// Primary keys and required fields must be filled in with
278 + /// non-null values before the record can be inserted.
279 + /// <para>The record is tied to this table in this database;
280 + /// it cannot be inserted into another table or database.</para>
281 + /// </remarks>
282 + public TRecord NewRecord()
283 + {
284 + TRecord rec = new TRecord();
285 + rec.Database = this.Database;
286 + rec.TableInfo = this.TableInfo;
287 + IList<string> values = new List<string>(this.TableInfo.Columns.Count);
288 + for (int i = 0; i < this.TableInfo.Columns.Count; i++)
289 + {
290 + values.Add(null);
291 + }
292 + rec.Values = values;
293 + return rec;
294 + }
295 + }
296 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs new
+992
@@ -0,0 +1,992 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Linq
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 + using System.Linq;
10 + using System.Linq.Expressions;
11 + using System.Text;
12 + using System.Reflection;
13 + using System.Globalization;
14 + using System.Diagnostics.CodeAnalysis;
15 +
16 + /// <summary>
17 + /// Implements the LINQ to MSI query functionality.
18 + /// </summary>
19 + /// <typeparam name="T">the result type of the current query --
20 + /// either some kind of QRecord, or some projection of record data</typeparam>
21 + internal sealed class Query<T> : IOrderedQueryable<T>, IQueryProvider
22 + {
23 + private QDatabase db;
24 + private Expression queryableExpression;
25 + private List<TableInfo> tables;
26 + private List<Type> recordTypes;
27 + private List<string> selectors;
28 + private string where;
29 + private List<object> whereParameters;
30 + private List<TableColumn> orderbyColumns;
31 + private List<TableColumn> selectColumns;
32 + private List<TableColumn> joinColumns;
33 + private List<Delegate> projectionDelegates;
34 +
35 + internal Query(QDatabase db, Expression expression)
36 + {
37 + if (expression == null)
38 + {
39 + throw new ArgumentNullException("expression");
40 + }
41 +
42 + this.db = db;
43 + this.queryableExpression = expression;
44 + this.tables = new List<TableInfo>();
45 + this.recordTypes = new List<Type>();
46 + this.selectors = new List<string>();
47 + this.whereParameters = new List<object>();
48 + this.orderbyColumns = new List<TableColumn>();
49 + this.selectColumns = new List<TableColumn>();
50 + this.joinColumns = new List<TableColumn>();
51 + this.projectionDelegates = new List<Delegate>();
52 + }
53 +
54 + public IEnumerator<T> GetEnumerator()
55 + {
56 + if (this.selectColumns.Count == 0)
57 + {
58 + AddAllColumns(this.tables[0], this.selectColumns);
59 + }
60 +
61 + string query = this.CompileQuery();
62 + return this.InvokeQuery(query);
63 + }
64 +
65 + private string CompileQuery()
66 + {
67 + bool explicitTables = this.tables.Count > 1;
68 +
69 + StringBuilder queryBuilder = new StringBuilder("SELECT");
70 +
71 + for (int i = 0; i < this.selectColumns.Count; i++)
72 + {
73 + queryBuilder.AppendFormat(
74 + CultureInfo.InvariantCulture,
75 + (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
76 + (i > 0 ? "," : String.Empty),
77 + this.selectColumns[i].Table.Name,
78 + this.selectColumns[i].Column.Name);
79 + }
80 +
81 + for (int i = 0; i < this.tables.Count; i++)
82 + {
83 + queryBuilder.AppendFormat(
84 + CultureInfo.InvariantCulture,
85 + "{0} `{1}`",
86 + (i == 0 ? " FROM" : ","),
87 + this.tables[i].Name);
88 + }
89 +
90 + bool startedWhere = false;
91 + for (int i = 0; i < this.joinColumns.Count - 1; i += 2)
92 + {
93 + queryBuilder.AppendFormat(
94 + CultureInfo.InvariantCulture,
95 + "{0} `{1}`.`{2}` = `{3}`.`{4}` ",
96 + (i == 0 ? " WHERE" : "AND"),
97 + this.joinColumns[i].Table,
98 + this.joinColumns[i].Column,
99 + this.joinColumns[i + 1].Table,
100 + this.joinColumns[i + 1].Column);
101 + startedWhere = true;
102 + }
103 +
104 + if (this.where != null)
105 + {
106 + queryBuilder.Append(startedWhere ? "AND " : " WHERE");
107 + queryBuilder.Append(this.where);
108 + }
109 +
110 + for (int i = 0; i < this.orderbyColumns.Count; i++)
111 + {
112 + VerifyOrderByColumn(this.orderbyColumns[i]);
113 +
114 + queryBuilder.AppendFormat(
115 + CultureInfo.InvariantCulture,
116 + (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
117 + (i == 0 ? " ORDER BY" : ","),
118 + this.orderbyColumns[i].Table.Name,
119 + this.orderbyColumns[i].Column.Name);
120 + }
121 +
122 + return queryBuilder.ToString();
123 + }
124 +
125 + private static void VerifyOrderByColumn(TableColumn tableColumn)
126 + {
127 + if (tableColumn.Column.Type != typeof(int) &&
128 + tableColumn.Column.Type != typeof(short))
129 + {
130 + throw new NotSupportedException(
131 + "Cannot orderby column: " + tableColumn.Column.Name +
132 + "; orderby is only supported on integer fields");
133 + }
134 + }
135 +
136 + private IEnumerator<T> InvokeQuery(string query)
137 + {
138 + TextWriter log = this.db.Log;
139 + if (log != null)
140 + {
141 + log.WriteLine();
142 + log.WriteLine(query);
143 + }
144 +
145 + using (View queryView = this.db.OpenView(query))
146 + {
147 + if (this.whereParameters != null && this.whereParameters.Count > 0)
148 + {
149 + using (Record paramsRec = this.db.CreateRecord(this.whereParameters.Count))
150 + {
151 + for (int i = 0; i < this.whereParameters.Count; i++)
152 + {
153 + paramsRec[i + 1] = this.whereParameters[i];
154 +
155 + if (log != null)
156 + {
157 + log.WriteLine(" ? = " + this.whereParameters[i]);
158 + }
159 + }
160 +
161 + queryView.Execute(paramsRec);
162 + }
163 + }
164 + else
165 + {
166 + queryView.Execute();
167 + }
168 +
169 + foreach (Record resultRec in queryView) using (resultRec)
170 + {
171 + yield return this.GetResult(resultRec);
172 + }
173 + }
174 + }
175 +
176 + private T GetResult(Record resultRec)
177 + {
178 + object[] results = new object[this.tables.Count];
179 +
180 + for (int i = 0; i < this.tables.Count; i++)
181 + {
182 + string[] values = new string[this.tables[i].Columns.Count];
183 + for (int j = 0; j < this.selectColumns.Count; j++)
184 + {
185 + TableColumn col = this.selectColumns[j];
186 + if (col.Table.Name == this.tables[i].Name)
187 + {
188 + int index = this.tables[i].Columns.IndexOf(
189 + col.Column.Name);
190 + if (index >= 0)
191 + {
192 + if (col.Column.Type == typeof(Stream))
193 + {
194 + values[index] = "[Binary Data]";
195 + }
196 + else
197 + {
198 + values[index] = resultRec.GetString(j + 1);
199 + }
200 + }
201 + }
202 + }
203 +
204 + QRecord result = (QRecord) this.recordTypes[i]
205 + .GetConstructor(Type.EmptyTypes).Invoke(null);
206 + result.Database = this.db;
207 + result.TableInfo = this.tables[i];
208 + result.Values = values;
209 + result.Exists = true;
210 + results[i] = result;
211 + }
212 +
213 + if (this.projectionDelegates.Count > 0)
214 + {
215 + object resultsProjection = results[0];
216 + for (int i = 1; i <= results.Length; i++)
217 + {
218 + if (i < results.Length)
219 + {
220 + resultsProjection = this.projectionDelegates[i - 1]
221 + .DynamicInvoke(new object[] { resultsProjection, results[i] });
222 + }
223 + else
224 + {
225 + resultsProjection = this.projectionDelegates[i - 1]
226 + .DynamicInvoke(resultsProjection);
227 + }
228 + }
229 +
230 + return (T) resultsProjection;
231 + }
232 + else
233 + {
234 + return (T) (object) results[0];
235 + }
236 + }
237 +
238 + IEnumerator IEnumerable.GetEnumerator()
239 + {
240 + return ((IEnumerable<T>) this).GetEnumerator();
241 + }
242 +
243 + public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
244 + {
245 + if (expression == null)
246 + {
247 + throw new ArgumentNullException("expression");
248 + }
249 +
250 + Query<TElement> q = new Query<TElement>(this.db, expression);
251 + q.tables.AddRange(this.tables);
252 + q.recordTypes.AddRange(this.recordTypes);
253 + q.selectors.AddRange(this.selectors);
254 + q.where = this.where;
255 + q.whereParameters.AddRange(this.whereParameters);
256 + q.orderbyColumns.AddRange(this.orderbyColumns);
257 + q.selectColumns.AddRange(this.selectColumns);
258 + q.joinColumns.AddRange(this.joinColumns);
259 + q.projectionDelegates.AddRange(this.projectionDelegates);
260 +
261 + MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
262 + string methodName = methodCallExpression.Method.Name;
263 + if (methodName == "Select")
264 + {
265 + LambdaExpression argumentExpression = (LambdaExpression)
266 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
267 + q.BuildProjection(null, argumentExpression);
268 + }
269 + else if (methodName == "Where")
270 + {
271 + LambdaExpression argumentExpression = (LambdaExpression)
272 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
273 + q.BuildQuery(null, argumentExpression);
274 + }
275 + else if (methodName == "ThenBy")
276 + {
277 + LambdaExpression argumentExpression = (LambdaExpression)
278 + ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
279 + q.BuildSequence(null, argumentExpression);
280 + }
281 + else if (methodName == "Join")
282 + {
283 + ConstantExpression constantExpression = (ConstantExpression)
284 + methodCallExpression.Arguments[1];
285 + IQueryable inner = (IQueryable) constantExpression.Value;
286 + q.PerformJoin(
287 + null,
288 + null,
289 + inner,
290 + GetJoinLambda(methodCallExpression.Arguments[2]),
291 + GetJoinLambda(methodCallExpression.Arguments[3]),
292 + GetJoinLambda(methodCallExpression.Arguments[4]));
293 + }
294 + else
295 + {
296 + throw new NotSupportedException(
297 + "Query operation not supported: " + methodName);
298 + }
299 +
300 + return q;
301 + }
302 +
303 + public IQueryable CreateQuery(Expression expression)
304 + {
305 + return this.CreateQuery<T>(expression);
306 + }
307 +
308 + private static LambdaExpression GetJoinLambda(Expression expresion)
309 + {
310 + UnaryExpression unaryExpression = (UnaryExpression) expresion;
311 + return (LambdaExpression) unaryExpression.Operand;
312 + }
313 +
314 + public TResult Execute<TResult>(Expression expression)
315 + {
316 + throw new NotSupportedException(
317 + "Direct method calls not supported -- use AsEnumerable() instead.");
318 + }
319 +
320 + object IQueryProvider.Execute(Expression expression)
321 + {
322 + throw new NotSupportedException(
323 + "Direct method calls not supported -- use AsEnumerable() instead.");
324 + }
325 +
326 + public IQueryProvider Provider
327 + {
328 + get
329 + {
330 + return this;
331 + }
332 + }
333 +
334 + public Type ElementType
335 + {
336 + get
337 + {
338 + return typeof(T);
339 + }
340 + }
341 +
342 + public Expression Expression
343 + {
344 + get
345 + {
346 + return this.queryableExpression;
347 + }
348 + }
349 +
350 + internal void BuildQuery(TableInfo tableInfo, LambdaExpression expression)
351 + {
352 + if (tableInfo != null)
353 + {
354 + this.tables.Add(tableInfo);
355 + this.recordTypes.Add(typeof(T));
356 + this.selectors.Add(expression.Parameters[0].Name);
357 + }
358 +
359 + StringBuilder queryBuilder = new StringBuilder();
360 +
361 + this.ParseQuery(expression.Body, queryBuilder);
362 +
363 + this.where = queryBuilder.ToString();
364 + }
365 +
366 + internal void BuildNullQuery(TableInfo tableInfo, Type recordType, LambdaExpression expression)
367 + {
368 + this.tables.Add(tableInfo);
369 + this.recordTypes.Add(recordType);
370 + this.selectors.Add(expression.Parameters[0].Name);
371 + }
372 +
373 + private void ParseQuery(Expression expression, StringBuilder queryBuilder)
374 + {
375 + queryBuilder.Append("(");
376 +
377 + BinaryExpression binaryExpression;
378 + UnaryExpression unaryExpression;
379 + MethodCallExpression methodCallExpression;
380 +
381 + if ((binaryExpression = expression as BinaryExpression) != null)
382 + {
383 + switch (binaryExpression.NodeType)
384 + {
385 + case ExpressionType.AndAlso:
386 + this.ParseQuery(binaryExpression.Left, queryBuilder);
387 + queryBuilder.Append(" AND ");
388 + this.ParseQuery(binaryExpression.Right, queryBuilder);
389 + break;
390 +
391 + case ExpressionType.OrElse:
392 + this.ParseQuery(binaryExpression.Left, queryBuilder);
393 + queryBuilder.Append(" OR ");
394 + this.ParseQuery(binaryExpression.Right, queryBuilder);
395 + break;
396 +
397 + case ExpressionType.Equal:
398 + case ExpressionType.NotEqual:
399 + case ExpressionType.GreaterThan:
400 + case ExpressionType.LessThan:
401 + case ExpressionType.GreaterThanOrEqual:
402 + case ExpressionType.LessThanOrEqual:
403 + this.ParseQueryCondition(binaryExpression, queryBuilder);
404 + break;
405 +
406 + default:
407 + throw new NotSupportedException(
408 + "Expression type not supported: " + binaryExpression.NodeType );
409 + }
410 + }
411 + else if ((unaryExpression = expression as UnaryExpression) != null)
412 + {
413 + throw new NotSupportedException(
414 + "Expression type not supported: " + unaryExpression.NodeType);
415 + }
416 + else if ((methodCallExpression = expression as MethodCallExpression) != null)
417 + {
418 + throw new NotSupportedException(
419 + "Method call not supported: " + methodCallExpression.Method.Name + "()");
420 + }
421 + else
422 + {
423 + throw new NotSupportedException(
424 + "Query filter expression not supported: " + expression);
425 + }
426 +
427 + queryBuilder.Append(")");
428 + }
429 +
430 + private static ExpressionType OppositeExpression(ExpressionType e)
431 + {
432 + switch (e)
433 + {
434 + case ExpressionType.LessThan:
435 + return ExpressionType.GreaterThan;
436 + case ExpressionType.LessThanOrEqual:
437 + return ExpressionType.GreaterThanOrEqual;
438 + case ExpressionType.GreaterThan:
439 + return ExpressionType.LessThan;
440 + case ExpressionType.GreaterThanOrEqual:
441 + return ExpressionType.LessThanOrEqual;
442 + default:
443 + return e;
444 + }
445 + }
446 +
447 + private static bool IsIntegerType(Type t)
448 + {
449 + return
450 + t == typeof(sbyte) ||
451 + t == typeof(byte) ||
452 + t == typeof(short) ||
453 + t == typeof(ushort) ||
454 + t == typeof(int) ||
455 + t == typeof(uint) ||
456 + t == typeof(long) ||
457 + t == typeof(ulong);
458 + }
459 +
460 + private void ParseQueryCondition(
461 + BinaryExpression binaryExpression, StringBuilder queryBuilder)
462 + {
463 + bool swap;
464 + string column = this.GetConditionColumn(binaryExpression, out swap);
465 + queryBuilder.Append(column);
466 +
467 + ExpressionType expressionType = binaryExpression.NodeType;
468 + if (swap)
469 + {
470 + expressionType = OppositeExpression(expressionType);
471 + }
472 +
473 + LambdaExpression valueExpression = Expression.Lambda(
474 + swap ? binaryExpression.Left : binaryExpression.Right);
475 + object value = valueExpression.Compile().DynamicInvoke();
476 +
477 + bool valueIsInt = false;
478 + if (value != null)
479 + {
480 + if (IsIntegerType(value.GetType()))
481 + {
482 + valueIsInt = true;
483 + }
484 + else
485 + {
486 + value = value.ToString();
487 + }
488 + }
489 +
490 + switch (expressionType)
491 + {
492 + case ExpressionType.Equal:
493 + if (value == null)
494 + {
495 + queryBuilder.Append(" IS NULL");
496 + }
497 + else if (valueIsInt)
498 + {
499 + queryBuilder.Append(" = ");
500 + queryBuilder.Append(value);
501 + }
502 + else
503 + {
504 + queryBuilder.Append(" = ?");
505 + this.whereParameters.Add(value);
506 + }
507 + return;
508 +
509 + case ExpressionType.NotEqual:
510 + if (value == null)
511 + {
512 + queryBuilder.Append(" IS NOT NULL");
513 + }
514 + else if (valueIsInt)
515 + {
516 + queryBuilder.Append(" <> ");
517 + queryBuilder.Append(value);
518 + }
519 + else
520 + {
521 + queryBuilder.Append(" <> ?");
522 + this.whereParameters.Add(value);
523 + }
524 + return;
525 + }
526 +
527 + if (value == null)
528 + {
529 + throw new InvalidOperationException(
530 + "A null value was used in a greater-than/less-than operation.");
531 + }
532 +
533 + if (!valueIsInt)
534 + {
535 + throw new NotSupportedException(
536 + "Greater-than/less-than operators not supported on strings.");
537 + }
538 +
539 + switch (expressionType)
540 + {
541 + case ExpressionType.LessThan:
542 + queryBuilder.Append(" < ");
543 + break;
544 +
545 + case ExpressionType.LessThanOrEqual:
546 + queryBuilder.Append(" <= ");
547 + break;
548 +
549 + case ExpressionType.GreaterThan:
550 + queryBuilder.Append(" > ");
551 + break;
552 +
553 + case ExpressionType.GreaterThanOrEqual:
554 + queryBuilder.Append(" >= ");
555 + break;
556 +
557 + default:
558 + throw new NotSupportedException(
559 + "Unsupported query expression type: " + expressionType);
560 + }
561 +
562 + queryBuilder.Append(value);
563 + }
564 +
565 + private string GetConditionColumn(
566 + BinaryExpression binaryExpression, out bool swap)
567 + {
568 + MemberExpression memberExpression;
569 + MethodCallExpression methodCallExpression;
570 +
571 + if (((memberExpression = binaryExpression.Left as MemberExpression) != null) ||
572 + ((binaryExpression.Left.NodeType == ExpressionType.Convert ||
573 + binaryExpression.Left.NodeType == ExpressionType.ConvertChecked) &&
574 + (memberExpression = ((UnaryExpression) binaryExpression.Left).Operand
575 + as MemberExpression) != null))
576 + {
577 + string column = this.GetConditionColumn(memberExpression);
578 + if (column != null)
579 + {
580 + swap = false;
581 + return column;
582 + }
583 + }
584 + else if (((memberExpression = binaryExpression.Right as MemberExpression) != null) ||
585 + ((binaryExpression.Right.NodeType == ExpressionType.Convert ||
586 + binaryExpression.Right.NodeType == ExpressionType.ConvertChecked) &&
587 + (memberExpression = ((UnaryExpression) binaryExpression.Right).Operand
588 + as MemberExpression) != null))
589 + {
590 + string column = this.GetConditionColumn(memberExpression);
591 + if (column != null)
592 + {
593 + swap = true;
594 + return column;
595 + }
596 + }
597 + else if ((methodCallExpression = binaryExpression.Left as MethodCallExpression) != null)
598 + {
599 + string column = this.GetConditionColumn(methodCallExpression);
600 + if (column != null)
601 + {
602 + swap = false;
603 + return column;
604 + }
605 + }
606 + else if ((methodCallExpression = binaryExpression.Right as MethodCallExpression) != null)
607 + {
608 + string column = this.GetConditionColumn(methodCallExpression);
609 + if (column != null)
610 + {
611 + swap = true;
612 + return column;
613 + }
614 + }
615 +
616 + throw new NotSupportedException(
617 + "Unsupported binary expression: " + binaryExpression);
618 + }
619 +
620 + private string GetConditionColumn(MemberExpression memberExpression)
621 + {
622 + string columnName = GetColumnName(memberExpression.Member);
623 + string selectorName = GetConditionSelectorName(memberExpression.Expression);
624 + string tableName = this.GetConditionTable(selectorName, columnName);
625 + return this.FormatColumn(tableName, columnName);
626 + }
627 +
628 + private string GetConditionColumn(MethodCallExpression methodCallExpression)
629 + {
630 + LambdaExpression argumentExpression =
631 + Expression.Lambda(methodCallExpression.Arguments[0]);
632 + string columnName = (string) argumentExpression.Compile().DynamicInvoke();
633 + string selectorName = GetConditionSelectorName(methodCallExpression.Object);
634 + string tableName = this.GetConditionTable(selectorName, columnName);
635 + return this.FormatColumn(tableName, columnName);
636 + }
637 +
638 + private static string GetConditionSelectorName(Expression expression)
639 + {
640 + ParameterExpression parameterExpression;
641 + MemberExpression memberExpression;
642 + if ((parameterExpression = expression as ParameterExpression) != null)
643 + {
644 + return parameterExpression.Name;
645 + }
646 + else if ((memberExpression = expression as MemberExpression) != null)
647 + {
648 + return memberExpression.Member.Name;
649 + }
650 + else
651 + {
652 + throw new NotSupportedException(
653 + "Unsupported conditional selector expression: " + expression);
654 + }
655 + }
656 +
657 + private string GetConditionTable(string selectorName, string columnName)
658 + {
659 + string tableName = null;
660 +
661 + for (int i = 0; i < this.tables.Count; i++)
662 + {
663 + if (this.selectors[i] == selectorName)
664 + {
665 + tableName = this.tables[i].Name;
666 + break;
667 + }
668 + }
669 +
670 + if (tableName == null)
671 + {
672 + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
673 + "Conditional expression contains column {0}.{1} " +
674 + "from a table that is not in the query.",
675 + selectorName,
676 + columnName));
677 + }
678 +
679 + return tableName;
680 + }
681 +
682 + private string FormatColumn(string tableName, string columnName)
683 + {
684 + if (tableName != null && this.tables.Count > 1)
685 + {
686 + return String.Format(CultureInfo.InvariantCulture, "`{0}`.`{1}`", tableName, columnName);
687 + }
688 + else
689 + {
690 + return String.Format(CultureInfo.InvariantCulture, "`{0}`", columnName);
691 + }
692 + }
693 +
694 + private static string GetColumnName(MemberInfo memberInfo)
695 + {
696 + foreach (var attr in memberInfo.GetCustomAttributes(
697 + typeof(DatabaseColumnAttribute), false))
698 + {
699 + return ((DatabaseColumnAttribute) attr).Column;
700 + }
701 +
702 + return memberInfo.Name;
703 + }
704 +
705 + internal void BuildProjection(TableInfo tableInfo, LambdaExpression expression)
706 + {
707 + if (tableInfo != null)
708 + {
709 + this.tables.Add(tableInfo);
710 + this.recordTypes.Add(typeof(T));
711 + this.selectors.Add(expression.Parameters[0].Name);
712 + }
713 +
714 + this.FindColumns(expression, this.selectColumns);
715 + this.projectionDelegates.Add(expression.Compile());
716 + }
717 +
718 + internal void BuildSequence(TableInfo tableInfo, LambdaExpression expression)
719 + {
720 + if (tableInfo != null)
721 + {
722 + this.tables.Add(tableInfo);
723 + this.recordTypes.Add(typeof(T));
724 + this.selectors.Add(expression.Parameters[0].Name);
725 + }
726 +
727 + this.FindColumns(expression.Body, this.orderbyColumns);
728 + }
729 +
730 + private static void AddAllColumns(TableInfo tableInfo, IList<TableColumn> columnList)
731 + {
732 + foreach (ColumnInfo column in tableInfo.Columns)
733 + {
734 + columnList.Add(new TableColumn(tableInfo, column));
735 + }
736 + }
737 +
738 + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
739 + private void FindColumns(Expression expression, IList<TableColumn> columnList)
740 + {
741 + if (expression is ParameterExpression)
742 + {
743 + ParameterExpression e = expression as ParameterExpression;
744 + string selector = e.Name;
745 + for (int i = 0; i < this.tables.Count; i++)
746 + {
747 + if (this.selectors[i] == selector)
748 + {
749 + AddAllColumns(this.tables[i], columnList);
750 + break;
751 + }
752 + }
753 + }
754 + else if (expression.NodeType == ExpressionType.MemberAccess)
755 + {
756 + this.FindColumns(expression as MemberExpression, columnList);
757 + }
758 + else if (expression is MethodCallExpression)
759 + {
760 + this.FindColumns(expression as MethodCallExpression, columnList);
761 + }
762 + else if (expression is BinaryExpression)
763 + {
764 + BinaryExpression e = expression as BinaryExpression;
765 + this.FindColumns(e.Left, columnList);
766 + this.FindColumns(e.Right, columnList);
767 + }
768 + else if (expression is UnaryExpression)
769 + {
770 + UnaryExpression e = expression as UnaryExpression;
771 + this.FindColumns(e.Operand, columnList);
772 + }
773 + else if (expression is ConditionalExpression)
774 + {
775 + ConditionalExpression e = expression as ConditionalExpression;
776 + this.FindColumns(e.Test, columnList);
777 + this.FindColumns(e.IfTrue, columnList);
778 + this.FindColumns(e.IfFalse, columnList);
779 + }
780 + else if (expression is InvocationExpression)
781 + {
782 + InvocationExpression e = expression as InvocationExpression;
783 + this.FindColumns(e.Expression, columnList);
784 + this.FindColumns(e.Arguments, columnList);
785 + }
786 + else if (expression is LambdaExpression)
787 + {
788 + LambdaExpression e = expression as LambdaExpression;
789 + this.FindColumns(e.Body, columnList);
790 + }
791 + else if (expression is ListInitExpression)
792 + {
793 + ListInitExpression e = expression as ListInitExpression;
794 + this.FindColumns(e.NewExpression, columnList);
795 + foreach (ElementInit ei in e.Initializers)
796 + {
797 + this.FindColumns(ei.Arguments, columnList);
798 + }
799 + }
800 + else if (expression is MemberInitExpression)
801 + {
802 + MemberInitExpression e = expression as MemberInitExpression;
803 + this.FindColumns(e.NewExpression, columnList);
804 + foreach (MemberAssignment b in e.Bindings)
805 + {
806 + this.FindColumns(b.Expression, columnList);
807 + }
808 + }
809 + else if (expression is NewExpression)
810 + {
811 + NewExpression e = expression as NewExpression;
812 + this.FindColumns(e.Arguments, columnList);
813 + }
814 + else if (expression is NewArrayExpression)
815 + {
816 + NewArrayExpression e = expression as NewArrayExpression;
817 + this.FindColumns(e.Expressions, columnList);
818 + }
819 + else if (expression is TypeBinaryExpression)
820 + {
821 + TypeBinaryExpression e = expression as TypeBinaryExpression;
822 + this.FindColumns(e.Expression, columnList);
823 + }
824 + }
825 +
826 + private void FindColumns(IEnumerable<Expression> expressions, IList<TableColumn> columnList)
827 + {
828 + foreach (Expression expression in expressions)
829 + {
830 + this.FindColumns(expression, columnList);
831 + }
832 + }
833 +
834 + private void FindColumns(MemberExpression memberExpression, IList<TableColumn> columnList)
835 + {
836 + string selector = null;
837 + MemberExpression objectMemberExpression;
838 + ParameterExpression objectParameterExpression;
839 + if ((objectParameterExpression = memberExpression.Expression as
840 + ParameterExpression) != null)
841 + {
842 + selector = objectParameterExpression.Name;
843 + }
844 + else if ((objectMemberExpression = memberExpression.Expression as
845 + MemberExpression) != null)
846 + {
847 + selector = objectMemberExpression.Member.Name;
848 + }
849 +
850 + if (selector != null)
851 + {
852 + for (int i = 0; i < this.tables.Count; i++)
853 + {
854 + if (this.selectors[i] == selector)
855 + {
856 + string columnName = GetColumnName(memberExpression.Member);
857 + ColumnInfo column = this.tables[i].Columns[columnName];
858 + columnList.Add(new TableColumn(this.tables[i], column));
859 + break;
860 + }
861 + }
862 + }
863 +
864 + selector = memberExpression.Member.Name;
865 + for (int i = 0; i < this.tables.Count; i++)
866 + {
867 + if (this.selectors[i] == selector)
868 + {
869 + AddAllColumns(this.tables[i], columnList);
870 + break;
871 + }
872 + }
873 + }
874 +
875 + private void FindColumns(MethodCallExpression methodCallExpression, IList<TableColumn> columnList)
876 + {
877 + if (methodCallExpression.Method.Name == "get_Item" &&
878 + methodCallExpression.Arguments.Count == 1 &&
879 + methodCallExpression.Arguments[0].Type == typeof(string))
880 + {
881 + string selector = null;
882 + MemberExpression objectMemberExpression;
883 + ParameterExpression objectParameterExpression;
884 + if ((objectParameterExpression = methodCallExpression.Object as ParameterExpression) != null)
885 + {
886 + selector = objectParameterExpression.Name;
887 + }
888 + else if ((objectMemberExpression = methodCallExpression.Object as MemberExpression) != null)
889 + {
890 + selector = objectMemberExpression.Member.Name;
891 + }
892 +
893 + if (selector != null)
894 + {
895 + for (int i = 0; i < this.tables.Count; i++)
896 + {
897 + if (this.selectors[i] == selector)
898 + {
899 + LambdaExpression argumentExpression =
900 + Expression.Lambda(methodCallExpression.Arguments[0]);
901 + string columnName = (string)
902 + argumentExpression.Compile().DynamicInvoke();
903 + ColumnInfo column = this.tables[i].Columns[columnName];
904 + columnList.Add(new TableColumn(this.tables[i], column));
905 + break;
906 + }
907 + }
908 + }
909 + }
910 +
911 + if (methodCallExpression.Object != null && methodCallExpression.Object.NodeType != ExpressionType.Parameter)
912 + {
913 + this.FindColumns(methodCallExpression.Object, columnList);
914 + }
915 + }
916 +
917 + internal void PerformJoin(
918 + TableInfo tableInfo,
919 + Type recordType,
920 + IQueryable joinTable,
921 + LambdaExpression outerKeySelector,
922 + LambdaExpression innerKeySelector,
923 + LambdaExpression resultSelector)
924 + {
925 + if (joinTable == null)
926 + {
927 + throw new ArgumentNullException("joinTable");
928 + }
929 +
930 + if (tableInfo != null)
931 + {
932 + this.tables.Add(tableInfo);
933 + this.recordTypes.Add(recordType);
934 + this.selectors.Add(outerKeySelector.Parameters[0].Name);
935 + }
936 +
937 + PropertyInfo tableInfoProp = joinTable.GetType().GetProperty("TableInfo");
938 + if (tableInfoProp == null)
939 + {
940 + throw new NotSupportedException(
941 + "Cannot join with object: " + joinTable.GetType().Name +
942 + "; join is only supported on another QTable.");
943 + }
944 +
945 + TableInfo joinTableInfo = (TableInfo) tableInfoProp.GetValue(joinTable, null);
946 + if (joinTableInfo == null)
947 + {
948 + throw new InvalidOperationException("Missing join table info.");
949 + }
950 +
951 + this.tables.Add(joinTableInfo);
952 + this.recordTypes.Add(joinTable.ElementType);
953 + this.selectors.Add(innerKeySelector.Parameters[0].Name);
954 + this.projectionDelegates.Add(resultSelector.Compile());
955 +
956 + int joinColumnCount = this.joinColumns.Count;
957 + this.FindColumns(outerKeySelector.Body, this.joinColumns);
958 + if (this.joinColumns.Count > joinColumnCount + 1)
959 + {
960 + throw new NotSupportedException("Join operations involving " +
961 + "multiple columns are not supported.");
962 + }
963 + else if (this.joinColumns.Count != joinColumnCount + 1)
964 + {
965 + throw new InvalidOperationException("Bad outer key selector for join.");
966 + }
967 +
968 + this.FindColumns(innerKeySelector.Body, this.joinColumns);
969 + if (this.joinColumns.Count > joinColumnCount + 2)
970 + {
971 + throw new NotSupportedException("Join operations involving " +
972 + "multiple columns not are supported.");
973 + }
974 + if (this.joinColumns.Count != joinColumnCount + 2)
975 + {
976 + throw new InvalidOperationException("Bad inner key selector for join.");
977 + }
978 + }
979 + }
980 +
981 + internal class TableColumn
982 + {
983 + public TableColumn(TableInfo table, ColumnInfo column)
984 + {
985 + this.Table = table;
986 + this.Column = column;
987 + }
988 +
989 + public TableInfo Table { get; set; }
990 + public ColumnInfo Column { get; set; }
991 + }
992 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/WixToolset.Dtf.WindowsInstaller.Linq.csproj new
+21
@@ -0,0 +1,21 @@
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 + <RootNamespace>WixToolset.Dtf.WindowsInstaller.Linq</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.WindowsInstaller.Linq</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net35</TargetFrameworks>
9 + <Description>LINQ extensions for Windows Installer classes</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
15 + </ItemGroup>
16 +
17 + <ItemGroup>
18 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 + </ItemGroup>
21 +</Project>
src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPackage.cs new
+1169
@@ -0,0 +1,1169 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Package
4 +{
5 +using System;
6 +using System.IO;
7 +using System.Text;
8 +using System.Collections;
9 +using System.Collections.Generic;
10 +using System.Diagnostics.CodeAnalysis;
11 +using System.Globalization;
12 +using System.Text.RegularExpressions;
13 +using WixToolset.Dtf.Compression;
14 +using WixToolset.Dtf.Compression.Cab;
15 +
16 +/// <summary>
17 +/// Handles status messages generated when operations are performed on an
18 +/// <see cref="InstallPackage"/> or <see cref="PatchPackage"/>.
19 +/// </summary>
20 +/// <example>
21 +/// <c>installPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
22 +/// </example>
23 +public delegate void InstallPackageMessageHandler(string format, params object[] args);
24 +
25 +/// <summary>
26 +/// Provides access to powerful build, maintenance, and analysis operations on an
27 +/// installation package (.MSI or .MSM).
28 +/// </summary>
29 +public class InstallPackage : Database
30 +{
31 + private string cabName;
32 + private string cabMsg;
33 +
34 + /// <summary>
35 + /// Creates a new InstallPackage object. The file source directory and working
36 + /// directory are the same as the location as the package file.
37 + /// </summary>
38 + /// <param name="packagePath">Path to the install package to be created or opened</param>
39 + /// <param name="openMode">Open mode for the database</param>
40 + public InstallPackage(string packagePath, DatabaseOpenMode openMode)
41 + : this(packagePath, openMode, null, null)
42 + {
43 + }
44 + /// <summary>
45 + /// Creates a new InstallPackage object, specifying an alternate file source
46 + /// directory and/or working directory.
47 + /// </summary>
48 + /// <param name="packagePath">Path to the install package to be created or opened</param>
49 + /// <param name="openMode">Open mode for the database</param>
50 + /// <param name="sourceDir">Location to obtain source files and cabinets when extracting
51 + /// or updating files in the working directory. This is often the location of an original
52 + /// copy of the package that is not meant to be modified. If this parameter is null, it
53 + /// defaults to the directory of <paramref name="packagePath"/>.</param>
54 + /// <param name="workingDir">Location where files will be extracted to/updated from. Also
55 + /// the location where a temporary folder is created during some operations. If this
56 + /// parameter is null, it defaults to the directory of <paramref name="packagePath"/>.</param>
57 + /// <remarks>If the source location is different than the working directory, then
58 + /// no files will be modified at the source location.
59 + /// </remarks>
60 + public InstallPackage(string packagePath, DatabaseOpenMode openMode,
61 + string sourceDir, string workingDir) : base(packagePath, openMode)
62 + {
63 + this.sourceDir = (sourceDir != null ? sourceDir : Path.GetDirectoryName(packagePath));
64 + this.workingDir = (workingDir != null ? workingDir : Path.GetDirectoryName(packagePath));
65 + this.compressionLevel = CompressionLevel.Normal;
66 +
67 + this.DeleteOnClose(this.TempDirectory);
68 + }
69 +
70 + /// <summary>
71 + /// Handle this event to receive status messages when operations are performed
72 + /// on the install package.
73 + /// </summary>
74 + /// <example>
75 + /// <c>installPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
76 + /// </example>
77 + public event InstallPackageMessageHandler Message;
78 +
79 + /// <summary>
80 + /// Sends a message to the <see cref="Message"/> event-handler.
81 + /// </summary>
82 + /// <param name="format">Message string, containing 0 or more format items</param>
83 + /// <param name="args">Items to be formatted</param>
84 + protected void LogMessage(string format, params object[] args)
85 + {
86 + if(this.Message != null)
87 + {
88 + this.Message(format, args);
89 + }
90 + }
91 +
92 + /// <summary>
93 + /// Gets or sets the location to obtain source files and cabinets when
94 + /// extracting or updating files in the working directory. This is often
95 + /// the location of an original copy of the package that is not meant
96 + /// to be modified.
97 + /// </summary>
98 + public string SourceDirectory
99 + {
100 + get { return this.sourceDir; }
101 + set { this.sourceDir = value; }
102 + }
103 + private string sourceDir;
104 +
105 + /// <summary>
106 + /// Gets or sets the location where files will be extracted to/updated from. Also
107 + /// the location where a temporary folder is created during some operations.
108 + /// </summary>
109 + public string WorkingDirectory
110 + {
111 + get { return this.workingDir; }
112 + set { this.workingDir = value; }
113 + }
114 + private string workingDir;
115 +
116 + private const string TEMP_DIR_NAME = "WITEMP";
117 +
118 + private string TempDirectory
119 + {
120 + get { return Path.Combine(this.WorkingDirectory, TEMP_DIR_NAME); }
121 + }
122 +
123 + /// <summary>
124 + /// Gets the list of file keys that have the specified long file name.
125 + /// </summary>
126 + /// <param name="longFileName">File name to search for (case-insensitive)</param>
127 + /// <returns>Array of file keys, or a 0-length array if none are found</returns>
128 + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
129 + public string[] FindFiles(string longFileName)
130 + {
131 + longFileName = longFileName.ToLowerInvariant();
132 + ArrayList fileList = new ArrayList();
133 + foreach(KeyValuePair<string, InstallPath> entry in this.Files)
134 + {
135 + if(((InstallPath) entry.Value).TargetName.ToLowerInvariant()
136 + == longFileName)
137 + {
138 + fileList.Add(entry.Key);
139 + }
140 + }
141 + return (string[]) fileList.ToArray(typeof(string));
142 + }
143 +
144 + /// <summary>
145 + /// Gets the list of file keys whose long file names match a specified
146 + /// regular-expression search pattern.
147 + /// </summary>
148 + /// <param name="pattern">Regular expression search pattern</param>
149 + /// <returns>Array of file keys, or a 0-length array if none are found</returns>
150 + public string[] FindFiles(Regex pattern)
151 + {
152 + ArrayList fileList = new ArrayList();
153 + foreach (KeyValuePair<string, InstallPath> entry in this.Files)
154 + {
155 + if(pattern.IsMatch(((InstallPath) entry.Value).TargetName))
156 + {
157 + fileList.Add(entry.Key);
158 + }
159 + }
160 + return (string[]) fileList.ToArray(typeof(string));
161 + }
162 +
163 + /// <summary>
164 + /// Extracts all files to the <see cref="WorkingDirectory"/>. The files are extracted
165 + /// to the relative directory matching their <see cref="InstallPath.SourcePath"/>.
166 + /// </summary>
167 + /// <remarks>If any files have the uncompressed attribute, they will be copied
168 + /// from the <see cref="SourceDirectory"/>.</remarks>
169 + public void ExtractFiles()
170 + {
171 + this.ExtractFiles(null);
172 + }
173 + /// <summary>
174 + /// Extracts a specified list of files to the <see cref="WorkingDirectory"/>. The files
175 + /// are extracted to the relative directory matching their <see cref="InstallPath.SourcePath"/>.
176 + /// </summary>
177 + /// <param name="fileKeys">List of file key strings to extract</param>
178 + /// <remarks>If any files have the uncompressed attribute, they will be copied
179 + /// from the <see cref="SourceDirectory"/>.</remarks>
180 + public void ExtractFiles(ICollection<string> fileKeys)
181 + {
182 + this.ProcessFilesByMediaDisk(fileKeys,
183 + new ProcessFilesOnOneMediaDiskHandler(this.ExtractFilesOnOneMediaDisk));
184 + }
185 +
186 + private bool IsMergeModule()
187 + {
188 + return this.CountRows("Media", "`LastSequence` >= 0") == 0 &&
189 + this.CountRows("_Streams", "`Name` = 'MergeModule.CABinet'") != 0;
190 + }
191 +
192 + private delegate void ProcessFilesOnOneMediaDiskHandler(string mediaCab,
193 + InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap);
194 +
195 + private void ProcessFilesByMediaDisk(ICollection<string> fileKeys,
196 + ProcessFilesOnOneMediaDiskHandler diskHandler)
197 + {
198 + if(this.IsMergeModule())
199 + {
200 + InstallPathMap files = new InstallPathMap();
201 + foreach(string fileKey in this.Files.Keys)
202 + {
203 + if(fileKeys == null || fileKeys.Contains(fileKey))
204 + {
205 + files[fileKey] = this.Files[fileKey];
206 + }
207 + }
208 + diskHandler("#MergeModule.CABinet", files, new InstallPathMap());
209 + }
210 + else
211 + {
212 + bool defaultCompressed = ((this.SummaryInfo.WordCount & 0x2) != 0);
213 +
214 + View fileView = null, mediaView = null;
215 + Record fileRec = null;
216 + try
217 + {
218 + fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
219 + "FROM `File` ORDER BY `Sequence`");
220 + mediaView = this.OpenView("SELECT `DiskId`, `LastSequence`, `Cabinet` " +
221 + "FROM `Media` ORDER BY `DiskId`");
222 + fileView.Execute();
223 + mediaView.Execute();
224 +
225 + int currentMediaDiskId = -1;
226 + int currentMediaMaxSequence = -1;
227 + string currentMediaCab = null;
228 + InstallPathMap compressedFileMap = new InstallPathMap();
229 + InstallPathMap uncompressedFileMap = new InstallPathMap();
230 +
231 + while((fileRec = fileView.Fetch()) != null)
232 + {
233 + string fileKey = (string) fileRec[1];
234 +
235 + if(fileKeys == null || fileKeys.Contains(fileKey))
236 + {
237 + int fileAttributes = fileRec.GetInteger(2);
238 + int fileSequence = fileRec.GetInteger(3);
239 +
240 + InstallPath fileInstallPath = this.Files[fileKey];
241 + if(fileInstallPath == null)
242 + {
243 + this.LogMessage("Could not get install path for source file: {0}", fileKey);
244 + throw new InstallerException("Could not get install path for source file: " + fileKey);
245 + }
246 +
247 + if(fileSequence > currentMediaMaxSequence)
248 + {
249 + if(currentMediaDiskId != -1)
250 + {
251 + diskHandler(currentMediaCab,
252 + compressedFileMap, uncompressedFileMap);
253 + compressedFileMap.Clear();
254 + uncompressedFileMap.Clear();
255 + }
256 +
257 + while(fileSequence > currentMediaMaxSequence)
258 + {
259 + Record mediaRec = mediaView.Fetch();
260 + if(mediaRec == null)
261 + {
262 + currentMediaDiskId = -1;
263 + break;
264 + }
265 + using(mediaRec)
266 + {
267 + currentMediaDiskId = mediaRec.GetInteger(1);
268 + currentMediaMaxSequence = mediaRec.GetInteger(2);
269 + currentMediaCab = (string) mediaRec[3];
270 + }
271 + }
272 + if(fileSequence > currentMediaMaxSequence) break;
273 + }
274 +
275 + if((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed) != 0)
276 + {
277 + compressedFileMap[fileKey] = fileInstallPath;
278 + }
279 + else if ((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed) != 0)
280 + {
281 + // Non-compressed files are located
282 + // in the same directory as the MSI, without any path.
283 + uncompressedFileMap[fileKey] = new InstallPath(fileInstallPath.SourceName);
284 + }
285 + else if(defaultCompressed)
286 + {
287 + compressedFileMap[fileKey] = fileInstallPath;
288 + }
289 + else
290 + {
291 + uncompressedFileMap[fileKey] = fileInstallPath;
292 + }
293 + }
294 + fileRec.Close();
295 + fileRec = null;
296 + }
297 + if(currentMediaDiskId != -1)
298 + {
299 + diskHandler(currentMediaCab,
300 + compressedFileMap, uncompressedFileMap);
301 + }
302 + }
303 + finally
304 + {
305 + if (fileRec != null) fileRec.Close();
306 + if (fileView != null) fileView.Close();
307 + if (mediaView != null) mediaView.Close();
308 + }
309 + }
310 + }
311 +
312 + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
313 + private void ExtractFilesOnOneMediaDisk(string mediaCab,
314 + InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
315 + {
316 + if(compressedFileMap.Count > 0)
317 + {
318 + string cabFile = null;
319 + if(mediaCab.StartsWith("#", StringComparison.Ordinal))
320 + {
321 + mediaCab = mediaCab.Substring(1);
322 +
323 + using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
324 + "WHERE `Name` = '{0}'", mediaCab))
325 + {
326 + streamView.Execute();
327 + Record streamRec = streamView.Fetch();
328 + if(streamRec == null)
329 + {
330 + this.LogMessage("Stream not found: {0}", mediaCab);
331 + throw new InstallerException("Stream not found: " + mediaCab);
332 + }
333 + using(streamRec)
334 + {
335 + this.LogMessage("extract cab {0}", mediaCab);
336 + Directory.CreateDirectory(this.TempDirectory);
337 + cabFile = Path.Combine(this.TempDirectory,
338 + Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
339 + streamRec.GetStream("Data", cabFile);
340 + }
341 + }
342 + }
343 + else
344 + {
345 + cabFile = Path.Combine(this.SourceDirectory, mediaCab);
346 + }
347 +
348 + this.cabName = mediaCab;
349 + this.cabMsg = "extract {0}\\{1} {2}";
350 + new CabInfo(cabFile).UnpackFileSet(compressedFileMap.SourcePaths, this.WorkingDirectory,
351 + this.CabinetProgress);
352 + ClearReadOnlyAttribute(this.WorkingDirectory, compressedFileMap.Values);
353 + }
354 + foreach(InstallPath fileInstallPath in uncompressedFileMap.Values)
355 + {
356 + string sourcePath = Path.Combine(this.SourceDirectory, fileInstallPath.SourcePath);
357 + string extractPath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
358 + if(Path.GetFullPath(sourcePath).ToLowerInvariant() !=
359 + Path.GetFullPath(extractPath).ToLowerInvariant())
360 + {
361 + if(!File.Exists(sourcePath))
362 + {
363 + this.LogMessage("Error: Uncompressed file not found: {0}", sourcePath);
364 + throw new FileNotFoundException("Uncompressed file not found.", sourcePath);
365 + }
366 + else
367 + {
368 + this.LogMessage("copy {0} {1}", sourcePath, extractPath);
369 + Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
370 + File.Copy(sourcePath, extractPath, true);
371 + }
372 + }
373 + else
374 + {
375 + if(!File.Exists(extractPath))
376 + {
377 + this.LogMessage("Error: Uncompressed file not found: {0}", extractPath);
378 + throw new FileNotFoundException("Uncompressed file not found.", extractPath);
379 + }
380 + }
381 + }
382 + }
383 +
384 + private void CabinetProgress(object sender, ArchiveProgressEventArgs e)
385 + {
386 + switch(e.ProgressType)
387 + {
388 + case ArchiveProgressType.StartFile:
389 + {
390 + string filePath = e.CurrentFileName;
391 + if(this.filePathMap != null)
392 + {
393 + InstallPath fileInstallPath = this.Files[e.CurrentFileName];
394 + if(fileInstallPath != null)
395 + {
396 + filePath = fileInstallPath.SourcePath;
397 + }
398 + }
399 + this.LogMessage(this.cabMsg, this.cabName, e.CurrentFileName,
400 + Path.Combine(this.WorkingDirectory, filePath));
401 + }
402 + break;
403 + }
404 + }
405 +
406 + /// <summary>
407 + /// Updates the install package with new files from the <see cref="WorkingDirectory"/>. The
408 + /// files must be in the relative directory matching their <see cref="InstallPath.SourcePath"/>.
409 + /// This method re-compresses and packages the files if necessary, and also updates the
410 + /// following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart*
411 + /// </summary>
412 + /// <remarks>
413 + /// The cabinet compression level used during re-cabbing can be configured with the
414 + /// <see cref="CompressionLevel"/> property.
415 + /// </remarks>
416 + public void UpdateFiles()
417 + {
418 + this.UpdateFiles(null);
419 + }
420 + /// <summary>
421 + /// Updates the install package with new files from the <see cref="WorkingDirectory"/>. The
422 + /// files must be in the relative directory matching their <see cref="InstallPath.SourcePath"/>.
423 + /// This method re-compresses and packages the files if necessary, and also updates the
424 + /// following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart?.
425 + /// </summary>
426 + /// <param name="fileKeys">List of file key strings to update</param>
427 + /// <remarks>
428 + /// This method does not change the media structure of the package, so it may require extracting
429 + /// and re-compressing a large cabinet just to update one file.
430 + /// <p>The cabinet compression level used during re-cabbing can be configured with the
431 + /// <see cref="CompressionLevel"/> property.</p>
432 + /// </remarks>
433 + public void UpdateFiles(ICollection<string> fileKeys)
434 + {
435 + this.ProcessFilesByMediaDisk(fileKeys,
436 + new ProcessFilesOnOneMediaDiskHandler(this.UpdateFilesOnOneMediaDisk));
437 + }
438 +
439 + private void UpdateFilesOnOneMediaDisk(string mediaCab,
440 + InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
441 + {
442 + if(compressedFileMap.Count > 0)
443 + {
444 + string cabFile = null;
445 + bool cabFileIsTemp = false;
446 + if(mediaCab.StartsWith("#", StringComparison.Ordinal))
447 + {
448 + cabFileIsTemp = true;
449 + mediaCab = mediaCab.Substring(1);
450 +
451 + using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
452 + "WHERE `Name` = '{0}'", mediaCab))
453 + {
454 + streamView.Execute();
455 + Record streamRec = streamView.Fetch();
456 + if(streamRec == null)
457 + {
458 + this.LogMessage("Stream not found: {0}", mediaCab);
459 + throw new InstallerException("Stream not found: " + mediaCab);
460 + }
461 + using(streamRec)
462 + {
463 + this.LogMessage("extract cab {0}", mediaCab);
464 + Directory.CreateDirectory(this.TempDirectory);
465 + cabFile = Path.Combine(this.TempDirectory,
466 + Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
467 + streamRec.GetStream("Data", cabFile);
468 + }
469 + }
470 + }
471 + else
472 + {
473 + cabFile = Path.Combine(this.SourceDirectory, mediaCab);
474 + }
475 +
476 + CabInfo cab = new CabInfo(cabFile);
477 + ArrayList fileKeyList = new ArrayList();
478 + foreach (CabFileInfo fileInCab in cab.GetFiles())
479 + {
480 + string fileKey = fileInCab.Name;
481 + if(this.Files[fileKey] != null)
482 + {
483 + fileKeyList.Add(fileKey);
484 + }
485 + }
486 + string[] fileKeys = (string[]) fileKeyList.ToArray(typeof(string));
487 +
488 + Directory.CreateDirectory(this.TempDirectory);
489 +
490 + ArrayList remainingFileKeys = new ArrayList(fileKeys);
491 + foreach(string fileKey in fileKeys)
492 + {
493 + InstallPath fileInstallPath = compressedFileMap[fileKey];
494 + if(fileInstallPath != null)
495 + {
496 + UpdateFileStats(fileKey, fileInstallPath);
497 +
498 + string filePath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
499 + this.LogMessage("copy {0} {1}", filePath, fileKey);
500 + File.Copy(filePath, Path.Combine(this.TempDirectory, fileKey), true);
501 + remainingFileKeys.Remove(fileKey);
502 + }
503 + }
504 +
505 + if(remainingFileKeys.Count > 0)
506 + {
507 + this.cabName = mediaCab;
508 + this.cabMsg = "extract {0}\\{1}";
509 + string[] remainingFileKeysArray = (string[]) remainingFileKeys.ToArray(typeof(string));
510 + cab.UnpackFiles(remainingFileKeysArray, this.TempDirectory, remainingFileKeysArray,
511 + this.CabinetProgress);
512 + }
513 +
514 + ClearReadOnlyAttribute(this.TempDirectory, fileKeys);
515 +
516 + if(!cabFileIsTemp)
517 + {
518 + cab = new CabInfo(Path.Combine(this.WorkingDirectory, mediaCab));
519 + }
520 + this.cabName = mediaCab;
521 + this.cabMsg = "compress {0}\\{1}";
522 + cab.PackFiles(this.TempDirectory, fileKeys, fileKeys,
523 + this.CompressionLevel, this.CabinetProgress);
524 +
525 + if(cabFileIsTemp)
526 + {
527 + using (Record streamRec = new Record(1))
528 + {
529 + streamRec.SetStream(1, cabFile);
530 + this.Execute(String.Format(
531 + "UPDATE `_Streams` SET `Data` = ? WHERE `Name` = '{0}'", mediaCab),
532 + streamRec);
533 + }
534 + }
535 + }
536 +
537 + foreach (KeyValuePair<string, InstallPath> entry in uncompressedFileMap)
538 + {
539 + UpdateFileStats((string) entry.Key, (InstallPath) entry.Value);
540 + }
541 + }
542 +
543 + private void UpdateFileStats(string fileKey, InstallPath fileInstallPath)
544 + {
545 + string filePath = Path.Combine(this.WorkingDirectory, fileInstallPath.SourcePath);
546 + if(!File.Exists(filePath))
547 + {
548 + this.LogMessage("Updated source file not found: {0}", filePath);
549 + throw new FileNotFoundException("Updated source file not found: " + filePath);
550 + }
551 +
552 + this.LogMessage("updatestats {0}", fileKey);
553 +
554 + string version = Installer.GetFileVersion(filePath);
555 + string language = Installer.GetFileLanguage(filePath);
556 + long size = new FileInfo(filePath).Length;
557 +
558 + this.Execute("UPDATE `File` SET `Version` = '{0}', `Language` = '{1}', " +
559 + "`FileSize` = {2} WHERE `File` = '{3}'", version, language, size, fileKey);
560 +
561 + if ((version == null || version.Length == 0) && this.Tables.Contains("MsiFileHash"))
562 + {
563 + int[] hash = new int[4];
564 + Installer.GetFileHash(filePath, hash);
565 + this.Execute("DELETE FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey);
566 + this.Execute("INSERT INTO `MsiFileHash` (`File_`, `Options`, `HashPart1`, `HashPart2`, " +
567 + "`HashPart3`, `HashPart4`) VALUES ('" + fileKey + "', 0, {0}, {1}, {2}, {3})",
568 + hash[0], hash[1], hash[2], hash[3]);
569 + }
570 + }
571 +
572 + /// <summary>
573 + /// Consolidates a package by combining and re-compressing all files into a single
574 + /// internal or external cabinet.
575 + /// </summary>
576 + /// <param name="mediaCabinet"></param>
577 + /// <remarks>If an installation package was built from many merge modules, this
578 + /// method can somewhat decrease package size, complexity, and installation time.
579 + /// <p>This method will also convert a package with all or mostly uncompressed
580 + /// files into a package where all files are compressed.</p>
581 + /// <p>If the package contains any not-yet-applied binary file patches (for
582 + /// example, a package generated by a call to <see cref="ApplyPatch"/>) then
583 + /// this method will apply the patches before compressing the updated files.</p>
584 + /// <p>This method edits the database summary information and the File, Media
585 + /// and Patch tables as necessary to maintain a valid installation package.</p>
586 + /// <p>The cabinet compression level used during re-cabbing can be configured with the
587 + /// <see cref="CompressionLevel"/> property.</p>
588 + /// </remarks>
589 + public void Consolidate(string mediaCabinet)
590 + {
591 + this.LogMessage("Consolidating package");
592 +
593 + Directory.CreateDirectory(this.TempDirectory);
594 +
595 + this.LogMessage("Extracting/preparing files");
596 + this.ProcessFilesByMediaDisk(null,
597 + new ProcessFilesOnOneMediaDiskHandler(this.PrepareOneMediaDiskForConsolidation));
598 +
599 + this.LogMessage("Applying any file patches");
600 + ApplyFilePatchesForConsolidation();
601 +
602 + this.LogMessage("Clearing PatchPackage, Patch, MsiPatchHeaders tables");
603 + if (this.Tables.Contains("PatchPackage"))
604 + {
605 + this.Execute("DELETE FROM `PatchPackage` WHERE `PatchId` <> ''");
606 + }
607 + if (this.Tables.Contains("Patch"))
608 + {
609 + this.Execute("DELETE FROM `Patch` WHERE `File_` <> ''");
610 + }
611 + if (this.Tables.Contains("MsiPatchHeaders"))
612 + {
613 + this.Execute("DELETE FROM `MsiPatchHeaders` WHERE `StreamRef` <> ''");
614 + }
615 +
616 + this.LogMessage("Resequencing files");
617 + ArrayList files = new ArrayList();
618 + using(View fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
619 + "FROM `File` ORDER BY `Sequence`"))
620 + {
621 + fileView.Execute();
622 +
623 + foreach (Record fileRec in fileView) using(fileRec)
624 + {
625 + files.Add(fileRec[1]);
626 + int fileAttributes = fileRec.GetInteger(2);
627 + fileAttributes &= ~(int) (WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed
628 + | WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed | WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded);
629 + fileRec[2] = fileAttributes;
630 + fileRec[3] = files.Count;
631 + fileView.Update(fileRec);
632 + }
633 + }
634 +
635 + bool internalCab = false;
636 + if(mediaCabinet.StartsWith("#", StringComparison.Ordinal))
637 + {
638 + internalCab = true;
639 + mediaCabinet = mediaCabinet.Substring(1);
640 + }
641 +
642 + this.LogMessage("Cabbing files");
643 + string[] fileKeys = (string[]) files.ToArray(typeof(string));
644 + string cabPath = Path.Combine(internalCab ? this.TempDirectory
645 + : this.WorkingDirectory, mediaCabinet);
646 + this.cabName = mediaCabinet;
647 + this.cabMsg = "compress {0}\\{1}";
648 + new CabInfo(cabPath).PackFiles(this.TempDirectory, fileKeys,
649 + fileKeys, this.CompressionLevel, this.CabinetProgress);
650 +
651 + this.DeleteEmbeddedCabs();
652 +
653 + if(internalCab)
654 + {
655 + this.LogMessage("Inserting cab stream into MSI");
656 + Record cabRec = new Record(1);
657 + cabRec.SetStream(1, cabPath);
658 + this.Execute("INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('" + mediaCabinet + "', ?)", cabRec);
659 + }
660 +
661 + this.LogMessage("Inserting cab media record into MSI");
662 + this.Execute("DELETE FROM `Media` WHERE `DiskId` <> 0");
663 + this.Execute("INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) " +
664 + "VALUES (1, " + files.Count + ", '" + (internalCab ? "#" : "") + mediaCabinet + "')");
665 +
666 +
667 + this.LogMessage("Setting compressed flag on package summary info");
668 + this.SummaryInfo.WordCount = this.SummaryInfo.WordCount | 2;
669 + this.SummaryInfo.Persist();
670 + }
671 +
672 + private void DeleteEmbeddedCabs()
673 + {
674 + using (View view = this.OpenView("SELECT `Cabinet` FROM `Media` WHERE `Cabinet` <> ''"))
675 + {
676 + view.Execute();
677 +
678 + foreach (Record rec in view) using(rec)
679 + {
680 + string cab = rec.GetString(1);
681 + if(cab.StartsWith("#", StringComparison.Ordinal))
682 + {
683 + cab = cab.Substring(1);
684 + this.LogMessage("Deleting embedded cab stream: {0}", cab);
685 + this.Execute("DELETE FROM `_Streams` WHERE `Name` = '{0}'", cab);
686 + }
687 + }
688 + }
689 + }
690 +
691 + private void PrepareOneMediaDiskForConsolidation(string mediaCab,
692 + InstallPathMap compressedFileMap, InstallPathMap uncompressedFileMap)
693 + {
694 + if(compressedFileMap.Count > 0)
695 + {
696 + string cabFile = null;
697 + if(mediaCab.StartsWith("#", StringComparison.Ordinal))
698 + {
699 + mediaCab = mediaCab.Substring(1);
700 +
701 + using (View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
702 + "WHERE `Name` = '{0}'", mediaCab))
703 + {
704 + streamView.Execute();
705 + Record streamRec = streamView.Fetch();
706 + if(streamRec == null)
707 + {
708 + this.LogMessage("Stream not found: {0}", mediaCab);
709 + throw new InstallerException("Stream not found: " + mediaCab);
710 + }
711 + using(streamRec)
712 + {
713 + this.LogMessage("extract cab {0}", mediaCab);
714 + cabFile = Path.Combine(this.TempDirectory,
715 + Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
716 + streamRec.GetStream("Data", cabFile);
717 + }
718 + }
719 + }
720 + else
721 + {
722 + cabFile = Path.Combine(this.SourceDirectory, mediaCab);
723 + }
724 + string[] fileKeys = new string[compressedFileMap.Keys.Count];
725 + compressedFileMap.Keys.CopyTo(fileKeys, 0);
726 + this.cabName = mediaCab;
727 + this.cabMsg = "extract {0}\\{1}";
728 + new CabInfo(cabFile).UnpackFiles(fileKeys, this.TempDirectory, fileKeys,
729 + this.CabinetProgress);
730 + ClearReadOnlyAttribute(this.TempDirectory, fileKeys);
731 + }
732 + foreach (KeyValuePair<string, InstallPath> entry in uncompressedFileMap)
733 + {
734 + string fileKey = (string) entry.Key;
735 + InstallPath fileInstallPath = (InstallPath) entry.Value;
736 +
737 + string filePath = Path.Combine(this.SourceDirectory, fileInstallPath.SourcePath);
738 + this.LogMessage("copy {0} {1}", filePath, fileKey);
739 + File.Copy(filePath, Path.Combine(this.TempDirectory, fileKey));
740 + }
741 + }
742 +
743 + private void ClearReadOnlyAttribute(string baseDirectory, IEnumerable filePaths)
744 + {
745 + foreach(object filePath in filePaths)
746 + {
747 + string fullFilePath = Path.Combine(baseDirectory, filePath.ToString());
748 + if (File.Exists(fullFilePath))
749 + {
750 + System.IO.FileAttributes fileAttributes = File.GetAttributes(fullFilePath);
751 + if ((fileAttributes & System.IO.FileAttributes.ReadOnly) != 0)
752 + {
753 + fileAttributes &= ~System.IO.FileAttributes.ReadOnly;
754 + File.SetAttributes(fullFilePath, fileAttributes);
755 + }
756 + }
757 + }
758 + }
759 +
760 + private void ApplyFilePatchesForConsolidation()
761 + {
762 + if(this.Tables.Contains("Patch"))
763 + {
764 + using(View patchView = this.OpenView("SELECT `File_`, `Sequence` " +
765 + "FROM `Patch` ORDER BY `Sequence`"))
766 + {
767 + patchView.Execute();
768 + Hashtable extractedPatchCabs = new Hashtable();
769 +
770 + foreach (Record patchRec in patchView) using(patchRec)
771 + {
772 + string fileKey = (string) patchRec[1];
773 + int sequence = patchRec.GetInteger(2);
774 + this.LogMessage("patch {0}", fileKey);
775 +
776 + string tempPatchFile = Path.Combine(this.TempDirectory, fileKey + ".pat");
777 + ExtractFilePatch(fileKey, sequence, tempPatchFile, extractedPatchCabs);
778 + string filePath = Path.Combine(this.TempDirectory, fileKey);
779 + string oldFilePath = filePath + ".old";
780 + if(File.Exists(oldFilePath)) File.Delete(oldFilePath);
781 + File.Move(filePath, oldFilePath);
782 + Type.GetType("WixToolset.Dtf.WindowsInstaller.FilePatch")
783 + .GetMethod("ApplyPatchToFile",
784 + new Type[] { typeof(string), typeof(string), typeof(string) })
785 + .Invoke(null, new object[] { tempPatchFile, oldFilePath, filePath });
786 + }
787 + }
788 + }
789 + }
790 +
791 + private void ExtractFilePatch(string fileKey, int sequence, string extractPath,
792 + IDictionary extractedCabs)
793 + {
794 + string mediaCab = null;
795 + using(View mediaView = this.OpenView("SELECT `DiskId`, `LastSequence`, `Cabinet` " +
796 + "FROM `Media` ORDER BY `DiskId`"))
797 + {
798 + mediaView.Execute();
799 +
800 + foreach (Record mediaRec in mediaView) using(mediaRec)
801 + {
802 + int mediaMaxSequence = mediaRec.GetInteger(2);
803 + if(mediaMaxSequence >= sequence)
804 + {
805 + mediaCab = mediaRec.GetString(3);
806 + break;
807 + }
808 + }
809 + }
810 +
811 + if(mediaCab == null || mediaCab.Length == 0)
812 + {
813 + this.LogMessage("Could not find cabinet for file patch: {0}", fileKey);
814 + throw new InstallerException("Could not find cabinet for file patch: " + fileKey);
815 + }
816 +
817 + if(!mediaCab.StartsWith("#", StringComparison.Ordinal))
818 + {
819 + this.LogMessage("Error: Patch cabinet {0} must be embedded", mediaCab);
820 + throw new InstallerException("Patch cabinet " + mediaCab + " must be embedded.");
821 + }
822 + mediaCab = mediaCab.Substring(1);
823 +
824 + string cabFile = (string) extractedCabs[mediaCab];
825 + if(cabFile == null)
826 + {
827 + using(View streamView = this.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
828 + "WHERE `Name` = '{0}'", mediaCab))
829 + {
830 + streamView.Execute();
831 + Record streamRec = streamView.Fetch();
832 + if(streamRec == null)
833 + {
834 + this.LogMessage("Stream not found: {0}", mediaCab);
835 + throw new InstallerException("Stream not found: " + mediaCab);
836 + }
837 + using(streamRec)
838 + {
839 + this.LogMessage("extract cab {0}", mediaCab);
840 + Directory.CreateDirectory(this.TempDirectory);
841 + cabFile = Path.Combine(this.TempDirectory,
842 + Path.GetFileNameWithoutExtension(mediaCab) + ".cab");
843 + streamRec.GetStream("Data", cabFile);
844 + }
845 + }
846 + extractedCabs[mediaCab] = cabFile;
847 + }
848 +
849 + this.LogMessage("extract patch {0}\\{1}", mediaCab, fileKey);
850 + new CabInfo(cabFile).UnpackFile(fileKey, extractPath);
851 + }
852 +
853 + /// <summary>
854 + /// Rebuilds the cached directory structure information accessed by the
855 + /// <see cref="Directories"/> and <see cref="Files"/> properties. This
856 + /// should be done after modifying the File, Component, or Directory
857 + /// tables, or else the cached information may no longer be accurate.
858 + /// </summary>
859 + public void UpdateDirectories()
860 + {
861 + this.dirPathMap = null;
862 + this.filePathMap = InstallPathMap.BuildFilePathMap(this,
863 + InstallPathMap.BuildComponentPathMap(this, this.Directories), false);
864 + }
865 +
866 + /// <summary>
867 + /// Gets a mapping from Directory keys to source/target paths.
868 + /// </summary>
869 + /// <remarks>
870 + /// If the Directory table is modified, this mapping
871 + /// will be outdated until you call <see cref="UpdateDirectories"/>.
872 + /// </remarks>
873 + public InstallPathMap Directories
874 + {
875 + get
876 + {
877 + if(this.dirPathMap == null)
878 + {
879 + this.dirPathMap = InstallPathMap.BuildDirectoryPathMap(this, false);
880 + }
881 + return this.dirPathMap;
882 + }
883 + }
884 + private InstallPathMap dirPathMap;
885 +
886 + /// <summary>
887 + /// Gets a mapping from File keys to source/target paths.
888 + /// </summary>
889 + /// <remarks>
890 + /// If the File, Component, or Directory tables are modified, this mapping
891 + /// may be outdated until you call <see cref="UpdateDirectories"/>.
892 + /// </remarks>
893 + public InstallPathMap Files
894 + {
895 + get
896 + {
897 + if(this.filePathMap == null)
898 + {
899 + this.filePathMap = InstallPathMap.BuildFilePathMap(this,
900 + InstallPathMap.BuildComponentPathMap(this, this.Directories), false);
901 + }
902 + return this.filePathMap;
903 + }
904 + }
905 + private InstallPathMap filePathMap;
906 +
907 + /// <summary>
908 + /// Gets or sets the compression level used by <see cref="UpdateFiles()"/>
909 + /// and <see cref="Consolidate"/>.
910 + /// </summary>
911 + /// <remarks>
912 + /// If the Directory table is modified, this mapping will be outdated
913 + /// until you close and reopen the install package.
914 + /// </remarks>
915 + public CompressionLevel CompressionLevel
916 + {
917 + get { return this.compressionLevel; }
918 + set { this.compressionLevel = value; }
919 + }
920 + private CompressionLevel compressionLevel;
921 +
922 + /// <summary>
923 + /// Applies a patch package to the database, resulting in an installation package that
924 + /// has the patch built-in.
925 + /// </summary>
926 + /// <param name="patchPackage">The patch package to be applied</param>
927 + /// <param name="transform">Optional name of the specific transform to apply.
928 + /// This parameter is usually left null, which causes the patch to be searched for
929 + /// a transform that is valid to apply to this database.</param>
930 + /// <remarks>
931 + /// If the patch contains any binary file patches, they will not immediately be applied
932 + /// to the target files, though they will at installation time.
933 + /// <p>After calling this method you can use <see cref="Consolidate"/> to apply
934 + /// the file patches immediately and also discard any outdated files from the package.</p>
935 + /// </remarks>
936 + public void ApplyPatch(PatchPackage patchPackage, string transform)
937 + {
938 + if(patchPackage == null) throw new ArgumentNullException("patchPackage");
939 +
940 + this.LogMessage("Applying patch file {0} to database {1}",
941 + patchPackage.FilePath, this.FilePath);
942 +
943 + if(transform == null)
944 + {
945 + this.LogMessage("No transform specified; searching for valid patch transform");
946 + string[] validTransforms = patchPackage.GetValidTransforms(this);
947 + if(validTransforms.Length == 0)
948 + {
949 + this.LogMessage("No valid patch transform was found");
950 + throw new InvalidOperationException("No valid patch transform was found.");
951 + }
952 + transform = validTransforms[0];
953 + }
954 + this.LogMessage("Patch transform = {0}", transform);
955 +
956 + string patchPrefix = Path.GetFileNameWithoutExtension(patchPackage.FilePath) + "_";
957 +
958 + string specialTransform = "#" + transform;
959 + Directory.CreateDirectory(this.TempDirectory);
960 + this.LogMessage("Extracting substorage {0}", transform);
961 + string transformFile = Path.Combine(this.TempDirectory,
962 + patchPrefix + Path.GetFileNameWithoutExtension(transform) + ".mst");
963 + patchPackage.ExtractTransform(transform, transformFile);
964 + this.LogMessage("Extracting substorage {0}", specialTransform);
965 + string specialTransformFile = Path.Combine(this.TempDirectory,
966 + patchPrefix + Path.GetFileNameWithoutExtension(specialTransform) + ".mst");
967 + patchPackage.ExtractTransform(specialTransform, specialTransformFile);
968 +
969 + if (this.Tables.Contains("Patch") && !this.Tables["Patch"].Columns.Contains("_StreamRef"))
970 + {
971 + if(this.CountRows("Patch") > 0)
972 + {
973 + this.LogMessage("Warning: non-empty Patch table exists without StreamRef_ column; " +
974 + "patch transform may fail");
975 + }
976 + else
977 + {
978 + this.Execute("DROP TABLE `Patch`");
979 + this.Execute("CREATE TABLE `Patch` (`File_` CHAR(72) NOT NULL, " +
980 + "`Sequence` INTEGER NOT NULL, `PatchSize` LONG NOT NULL, " +
981 + "`Attributes` INTEGER NOT NULL, `Header` OBJECT, `StreamRef_` CHAR(72) " +
982 + "PRIMARY KEY `File_`, `Sequence`)");
983 + }
984 + }
985 +
986 + this.LogMessage("Applying transform {0} to database", transform);
987 + this.ApplyTransform(transformFile);
988 + this.LogMessage("Applying transform {0} to database", specialTransform);
989 + this.ApplyTransform(specialTransformFile);
990 +
991 + if (this.Tables.Contains("MsiPatchHeaders") && this.CountRows("MsiPatchHeaders") > 0 &&
992 + (!this.Tables.Contains("Patch") || this.CountRows("Patch", "`StreamRef_` <> ''") == 0))
993 + {
994 + this.LogMessage("Error: patch transform failed because of missing Patch.StreamRef_ column");
995 + throw new InstallerException("Patch transform failed because of missing Patch.StreamRef_ column");
996 + }
997 +
998 + IList<int> mediaIds = this.ExecuteIntegerQuery("SELECT `Media_` FROM `PatchPackage` " +
999 + "WHERE `PatchId` = '{0}'", patchPackage.PatchCode);
1000 + if (mediaIds.Count == 0)
1001 + {
1002 + this.LogMessage("Warning: PatchPackage Media record not found -- " +
1003 + "skipping inclusion of patch cabinet");
1004 + }
1005 + else
1006 + {
1007 + int patchMediaDiskId = mediaIds[0];
1008 + IList<string> patchCabinets = this.ExecuteStringQuery("SELECT `Cabinet` FROM `Media` " +
1009 + "WHERE `DiskId` = {0}", patchMediaDiskId);
1010 + if(patchCabinets.Count == 0)
1011 + {
1012 + this.LogMessage("Patch cabinet record not found");
1013 + throw new InstallerException("Patch cabinet record not found.");
1014 + }
1015 + string patchCabinet = patchCabinets[0];
1016 + this.LogMessage("Patch cabinet = {0}", patchCabinet);
1017 + if(!patchCabinet.StartsWith("#", StringComparison.Ordinal))
1018 + {
1019 + this.LogMessage("Error: Patch cabinet must be embedded");
1020 + throw new InstallerException("Patch cabinet must be embedded.");
1021 + }
1022 + patchCabinet = patchCabinet.Substring(1);
1023 +
1024 + string renamePatchCabinet = patchPrefix + patchCabinet;
1025 +
1026 + const int HIGH_DISKID = 30000; // Must not collide with other patch media DiskIDs
1027 + int renamePatchMediaDiskId = HIGH_DISKID;
1028 + while (this.CountRows("Media", "`DiskId` = " + renamePatchMediaDiskId) > 0) renamePatchMediaDiskId++;
1029 +
1030 + // Since the patch cab is now embedded in the MSI, it shouldn't have a separate disk prompt/source
1031 + this.LogMessage("Renaming the patch media record");
1032 + int lastSeq = Convert.ToInt32(this.ExecuteScalar("SELECT `LastSequence` FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId));
1033 + this.Execute("DELETE FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId);
1034 + this.Execute("INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES ({0}, '{1}', '#{2}')",
1035 + renamePatchMediaDiskId, lastSeq, renamePatchCabinet);
1036 + this.Execute("UPDATE `PatchPackage` SET `Media_` = {0} WHERE `PatchId` = '{1}'", renamePatchMediaDiskId, patchPackage.PatchCode);
1037 +
1038 + this.LogMessage("Copying patch cabinet: {0}", patchCabinet);
1039 + string patchCabFile = Path.Combine(this.TempDirectory,
1040 + Path.GetFileNameWithoutExtension(patchCabinet) + ".cab");
1041 + using(View streamView = patchPackage.OpenView("SELECT `Name`, `Data` FROM `_Streams` " +
1042 + "WHERE `Name` = '{0}'", patchCabinet))
1043 + {
1044 + streamView.Execute();
1045 + Record streamRec = streamView.Fetch();
1046 + if(streamRec == null)
1047 + {
1048 + this.LogMessage("Error: Patch cabinet not found");
1049 + throw new InstallerException("Patch cabinet not found.");
1050 + }
1051 + using(streamRec)
1052 + {
1053 + streamRec.GetStream(2, patchCabFile);
1054 + }
1055 + }
1056 + using(Record patchCabRec = new Record(2))
1057 + {
1058 + patchCabRec[1] = patchCabinet;
1059 + patchCabRec.SetStream(2, patchCabFile);
1060 + this.Execute("INSERT INTO `_Streams` (`Name`, `Data`) VALUES (?, ?)", patchCabRec);
1061 + }
1062 +
1063 + this.LogMessage("Ensuring PatchFiles action exists in InstallExecuteSequence table");
1064 + if (this.Tables.Contains("InstallExecuteSequence"))
1065 + {
1066 + if(this.CountRows("InstallExecuteSequence", "`Action` = 'PatchFiles'") == 0)
1067 + {
1068 + IList<int> installFilesSeqList = this.ExecuteIntegerQuery("SELECT `Sequence` " +
1069 + "FROM `InstallExecuteSequence` WHERE `Action` = 'InstallFiles'");
1070 + short installFilesSeq = (short) (installFilesSeqList.Count != 0 ?
1071 + installFilesSeqList[0] : 0);
1072 + this.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) " +
1073 + "VALUES ('PatchFiles', {0})", installFilesSeq + 1);
1074 + }
1075 + }
1076 +
1077 + // Patch-added files need to be marked always-compressed
1078 + this.LogMessage("Adjusting attributes of patch-added files");
1079 + using(View fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " +
1080 + "FROM `File` ORDER BY `Sequence`"))
1081 + {
1082 + fileView.Execute();
1083 +
1084 + foreach (Record fileRec in fileView) using(fileRec)
1085 + {
1086 + int fileAttributes = fileRec.GetInteger(2);
1087 + if ((fileAttributes & (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded) != 0)
1088 + {
1089 + fileAttributes = (fileAttributes | (int) WixToolset.Dtf.WindowsInstaller.FileAttributes.Compressed)
1090 + & ~(int) WixToolset.Dtf.WindowsInstaller.FileAttributes.NonCompressed
1091 + & ~(int) WixToolset.Dtf.WindowsInstaller.FileAttributes.PatchAdded;
1092 + fileRec[2] = fileAttributes;
1093 + fileView.Update(fileRec);
1094 + }
1095 + }
1096 + }
1097 + }
1098 +
1099 + this.LogMessage("Applying new summary info from patch package");
1100 + this.SummaryInfo.RevisionNumber = this.Property["PATCHNEWPACKAGECODE"];
1101 + this.SummaryInfo.Subject = this.Property["PATCHNEWSUMMARYSUBJECT"];
1102 + this.SummaryInfo.Comments = this.Property["PATCHNEWSUMMARYCOMMENTS"];
1103 + this.SummaryInfo.Persist();
1104 + this.Property["PATCHNEWPACKAGECODE" ] = null;
1105 + this.Property["PATCHNEWSUMMARYSUBJECT" ] = null;
1106 + this.Property["PATCHNEWSUMMARYCOMMENTS"] = null;
1107 +
1108 + this.LogMessage("Patch application finished");
1109 + }
1110 +
1111 + /// <summary>
1112 + /// Accessor for getting and setting properties of the InstallPackage database.
1113 + /// </summary>
1114 + public InstallPackageProperties Property
1115 + {
1116 + get
1117 + {
1118 + if(this.properties == null)
1119 + {
1120 + this.properties = new InstallPackageProperties(this);
1121 + }
1122 + return this.properties;
1123 + }
1124 + }
1125 + private InstallPackageProperties properties = null;
1126 +}
1127 +
1128 +/// <summary>
1129 +/// Accessor for getting and setting properties of the <see cref="InstallPackage"/> database.
1130 +/// </summary>
1131 +public class InstallPackageProperties
1132 +{
1133 + internal InstallPackageProperties(InstallPackage installPackage)
1134 + {
1135 + this.installPackage = installPackage;
1136 + }
1137 + private InstallPackage installPackage;
1138 +
1139 + /// <summary>
1140 + /// Gets or sets a property in the database. When getting a property
1141 + /// that does not exist in the database, an empty string is returned.
1142 + /// To remove a property from the database, set it to an empty string.
1143 + /// </summary>
1144 + /// <remarks>
1145 + /// This has the same results as direct SQL queries on the Property table; it's only
1146 + /// meant to be a more convenient way of access.
1147 + /// </remarks>
1148 + public string this[string name]
1149 + {
1150 + get
1151 + {
1152 + IList<string> values = installPackage.ExecuteStringQuery(
1153 + "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", name);
1154 + return (values.Count != 0 ? values[0] : "");
1155 + }
1156 + set
1157 + {
1158 + Record propRec = new Record(name, (value != null ? value : ""));
1159 + installPackage.Execute("DELETE FROM `Property` WHERE `Property` = ?", propRec);
1160 + if(value != null && value.Length != 0)
1161 + {
1162 + installPackage.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)",
1163 + propRec);
1164 + }
1165 + }
1166 + }
1167 +}
1168 +
1169 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Package/InstallPath.cs new
+1073
@@ -0,0 +1,1073 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Package
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 + using System.Globalization;
10 +
11 + /// <summary>
12 + /// Represents the installation path of a file or directory from an installer product database.
13 + /// </summary>
14 + public class InstallPath
15 + {
16 + /// <summary>
17 + /// Creates a new InstallPath, specifying a filename.
18 + /// </summary>
19 + /// <param name="name">The name of the file or directory. Not a full path.</param>
20 + public InstallPath(string name) : this(name, false) { }
21 +
22 + /// <summary>
23 + /// Creates a new InstallPath, parsing out either the short or long filename.
24 + /// </summary>
25 + /// <param name="name">The name of the file or directory, in short|long syntax for a filename
26 + /// or targetshort|targetlong:sourceshort|sourcelong syntax for a directory.</param>
27 + /// <param name="useShortNames">true to parse the short part of the combined filename; false to parse the long part</param>
28 + public InstallPath(string name, bool useShortNames)
29 + {
30 + if(name == null)
31 + {
32 + throw new ArgumentNullException();
33 + }
34 + this.parentPath = null;
35 + ParseName(name, useShortNames);
36 + }
37 +
38 + private void ParseName(string name, bool useShortNames)
39 + {
40 + string[] parse = name.Split(new char[] { ':' }, 3);
41 + if(parse.Length == 3)
42 + {
43 + // Syntax was targetshort:sourceshort|targetlong:sourcelong.
44 + // Chnage it to targetshort|targetlong:sourceshort|sourcelong.
45 + parse = name.Split(new char[] { ':', '|' }, 4);
46 + if(parse.Length == 4)
47 + parse = new string[] { parse[0] + '|' + parse[2], parse[1] + '|' + parse[3] };
48 + else
49 + parse = new string[] { parse[0] + '|' + parse[1], parse[1] + '|' + parse[2] };
50 + }
51 + string targetName = parse[0];
52 + string sourceName = (parse.Length == 2 ? parse[1] : parse[0]);
53 + parse = targetName.Split(new char[] { '|' }, 2);
54 + if(parse.Length == 2) targetName = (useShortNames ? parse[0] : parse[1]);
55 + parse = sourceName.Split(new char[] { '|' }, 2);
56 + if(parse.Length == 2) sourceName = (useShortNames ? parse[0] : parse[1]);
57 +
58 + this.SourceName = sourceName;
59 + this.TargetName = targetName;
60 + }
61 +
62 + /// <summary>
63 + /// Gets the path of the parent directory.
64 + /// </summary>
65 + public InstallPath ParentPath
66 + {
67 + get
68 + {
69 + return parentPath;
70 + }
71 + }
72 + internal void SetParentPath(InstallPath value)
73 + {
74 + parentPath = value;
75 + ResetSourcePath();
76 + ResetTargetPath();
77 + }
78 + private InstallPath parentPath;
79 +
80 + /// <summary>
81 + /// Gets the set of child paths if this InstallPath object represents a a directory.
82 + /// </summary>
83 + public InstallPathCollection ChildPaths
84 + {
85 + get
86 + {
87 + if(childPaths == null)
88 + {
89 + childPaths = new InstallPathCollection(this);
90 + }
91 + return childPaths;
92 + }
93 + }
94 + private InstallPathCollection childPaths;
95 +
96 + /// <summary>
97 + /// Gets or sets the source name of the InstallPath.
98 + /// </summary>
99 + public string SourceName
100 + {
101 + get
102 + {
103 + return sourceName;
104 + }
105 + set
106 + {
107 + if(value == null)
108 + {
109 + throw new ArgumentNullException();
110 + }
111 + sourceName = value;
112 + ResetSourcePath();
113 + }
114 + }
115 + private string sourceName;
116 +
117 + /// <summary>
118 + /// Gets or sets the target name of the install path.
119 + /// </summary>
120 + public string TargetName
121 + {
122 + get
123 + {
124 + return targetName;
125 + }
126 + set
127 + {
128 + if(value == null)
129 + {
130 + throw new ArgumentNullException();
131 + }
132 + targetName = value;
133 + ResetTargetPath();
134 + }
135 + }
136 + private string targetName;
137 +
138 + /// <summary>
139 + /// Gets the full source path.
140 + /// </summary>
141 + public string SourcePath
142 + {
143 + get
144 + {
145 + if(sourcePath == null)
146 + {
147 + if(parentPath != null)
148 + {
149 + sourcePath = (sourceName.Equals(".") ? parentPath.SourcePath
150 + : Path.Combine(parentPath.SourcePath, sourceName));
151 + }
152 + else
153 + {
154 + sourcePath = sourceName;
155 + }
156 + }
157 + return sourcePath;
158 + }
159 + set
160 + {
161 + ResetSourcePath();
162 + sourcePath = value;
163 + }
164 + }
165 + private string sourcePath;
166 +
167 + /// <summary>
168 + /// Gets the full target path.
169 + /// </summary>
170 + public string TargetPath
171 + {
172 + get
173 + {
174 + if(targetPath == null)
175 + {
176 + if(parentPath != null)
177 + {
178 + targetPath = (targetName.Equals(".") ? parentPath.TargetPath
179 + : Path.Combine(parentPath.TargetPath, targetName));
180 + }
181 + else
182 + {
183 + targetPath = targetName;
184 + }
185 + }
186 + return targetPath;
187 + }
188 + set
189 + {
190 + ResetTargetPath();
191 + targetPath = value;
192 + }
193 + }
194 + private string targetPath;
195 +
196 + private void ResetSourcePath()
197 + {
198 + if(sourcePath != null)
199 + {
200 + sourcePath = null;
201 + if(childPaths != null)
202 + {
203 + foreach(InstallPath ip in childPaths)
204 + {
205 + ip.ResetSourcePath();
206 + }
207 + }
208 + }
209 + }
210 +
211 + private void ResetTargetPath()
212 + {
213 + if(targetPath != null)
214 + {
215 + targetPath = null;
216 + if(childPaths != null)
217 + {
218 + foreach(InstallPath ip in childPaths)
219 + {
220 + ip.ResetTargetPath();
221 + }
222 + }
223 + }
224 + }
225 +
226 + /// <summary>
227 + /// Gets the full source path.
228 + /// </summary>
229 + /// <returns><see cref="SourcePath"/></returns>
230 + public override String ToString()
231 + {
232 + return SourcePath;
233 + }
234 + }
235 +
236 + /// <summary>
237 + /// Represents a collection of InstallPaths that are the child paths of the same parent directory.
238 + /// </summary>
239 + public class InstallPathCollection : IList<InstallPath>
240 + {
241 + private InstallPath parentPath;
242 + private List<InstallPath> items;
243 +
244 + internal InstallPathCollection(InstallPath parentPath)
245 + {
246 + this.parentPath = parentPath;
247 + this.items = new List<InstallPath>();
248 + }
249 +
250 + /// <summary>
251 + /// Gets or sets the element at the specified index.
252 + /// </summary>
253 + public InstallPath this[int index]
254 + {
255 + get
256 + {
257 + return this.items[index];
258 + }
259 + set
260 + {
261 + this.OnSet(this.items[index], value);
262 + this.items[index] = value;
263 + }
264 + }
265 +
266 + /// <summary>
267 + /// Adds a new child path to the collection.
268 + /// </summary>
269 + /// <param name="item">The InstallPath to add.</param>
270 + public void Add(InstallPath item)
271 + {
272 + this.OnInsert(item);
273 + this.items.Add(item);
274 + }
275 +
276 + /// <summary>
277 + /// Removes a child path to the collection.
278 + /// </summary>
279 + /// <param name="item">The InstallPath to remove.</param>
280 + public bool Remove(InstallPath item)
281 + {
282 + int index = this.items.IndexOf(item);
283 + if (index >= 0)
284 + {
285 + this.OnRemove(item);
286 + this.items.RemoveAt(index);
287 + return true;
288 + }
289 + else
290 + {
291 + return false;
292 + }
293 + }
294 +
295 + /// <summary>
296 + /// Gets the index of a child path in the collection.
297 + /// </summary>
298 + /// <param name="item">The InstallPath to search for.</param>
299 + /// <returns>The index of the item, or -1 if not found.</returns>
300 + public int IndexOf(InstallPath item)
301 + {
302 + return this.items.IndexOf(item);
303 + }
304 +
305 + /// <summary>
306 + /// Inserts a child path into the collection.
307 + /// </summary>
308 + /// <param name="index">The insertion index.</param>
309 + /// <param name="item">The InstallPath to insert.</param>
310 + public void Insert(int index, InstallPath item)
311 + {
312 + this.OnInsert(item);
313 + this.items.Insert(index, item);
314 + }
315 +
316 + /// <summary>
317 + /// Tests if the collection contains a child path.
318 + /// </summary>
319 + /// <param name="item">The InstallPath to search for.</param>
320 + /// <returns>true if the item is found; false otherwise</returns>
321 + public bool Contains(InstallPath item)
322 + {
323 + return this.items.Contains(item);
324 + }
325 +
326 + /// <summary>
327 + /// Copies the collection into an array.
328 + /// </summary>
329 + /// <param name="array">The array to copy into.</param>
330 + /// <param name="index">The starting index in the destination array.</param>
331 + public void CopyTo(InstallPath[] array, int index)
332 + {
333 + this.items.CopyTo(array, index);
334 + }
335 +
336 + private void OnInsert(InstallPath item)
337 + {
338 + if (item.ParentPath != null)
339 + {
340 + item.ParentPath.ChildPaths.Remove(item);
341 + }
342 +
343 + item.SetParentPath(this.parentPath);
344 + }
345 +
346 + private void OnRemove(InstallPath item)
347 + {
348 + item.SetParentPath(null);
349 + }
350 +
351 + private void OnSet(InstallPath oldItem, InstallPath newItem)
352 + {
353 + this.OnRemove(oldItem);
354 + this.OnInsert(newItem);
355 + }
356 +
357 + /// <summary>
358 + /// Removes an item from the collection.
359 + /// </summary>
360 + /// <param name="index">The index of the item to remove.</param>
361 + public void RemoveAt(int index)
362 + {
363 + this.OnRemove(this[index]);
364 + this.items.RemoveAt(index);
365 + }
366 +
367 + /// <summary>
368 + /// Removes all items from the collection.
369 + /// </summary>
370 + public void Clear()
371 + {
372 + foreach (InstallPath item in this)
373 + {
374 + this.OnRemove(item);
375 + }
376 +
377 + this.items.Clear();
378 + }
379 +
380 + /// <summary>
381 + /// Gets the number of items in the collection.
382 + /// </summary>
383 + public int Count
384 + {
385 + get
386 + {
387 + return this.items.Count;
388 + }
389 + }
390 +
391 + bool ICollection<InstallPath>.IsReadOnly
392 + {
393 + get
394 + {
395 + return false;
396 + }
397 + }
398 +
399 + /// <summary>
400 + /// Gets an enumerator over all items in the collection.
401 + /// </summary>
402 + /// <returns>An enumerator for the collection.</returns>
403 + public IEnumerator<InstallPath> GetEnumerator()
404 + {
405 + return this.items.GetEnumerator();
406 + }
407 +
408 + IEnumerator IEnumerable.GetEnumerator()
409 + {
410 + return ((IEnumerable<InstallPath>) this).GetEnumerator();
411 + }
412 + }
413 +
414 + /// <summary>
415 + /// Represents a mapping of install paths for all directories, components, or files in
416 + /// an installation database.
417 + /// </summary>
418 + public class InstallPathMap : IDictionary<string, InstallPath>
419 + {
420 + /// <summary>
421 + /// Builds a mapping from File keys to installation paths.
422 + /// </summary>
423 + /// <param name="db">Installation database.</param>
424 + /// <param name="componentPathMap">Component mapping returned by <see cref="BuildComponentPathMap"/>.</param>
425 + /// <param name="useShortNames">true to use short file names; false to use long names</param>
426 + /// <returns>An InstallPathMap with the described mapping.</returns>
427 + public static InstallPathMap BuildFilePathMap(Database db, InstallPathMap componentPathMap,
428 + bool useShortNames)
429 + {
430 + if(db == null)
431 + {
432 + throw new ArgumentNullException("db");
433 + }
434 +
435 + if(componentPathMap == null)
436 + {
437 + componentPathMap = BuildComponentPathMap(db, BuildDirectoryPathMap(db, useShortNames));
438 + }
439 +
440 + InstallPathMap filePathMap = new InstallPathMap();
441 +
442 + using (View fileView = db.OpenView("SELECT `File`, `Component_`, `FileName` FROM `File`"))
443 + {
444 + fileView.Execute();
445 +
446 + foreach (Record fileRec in fileView)
447 + {
448 + using (fileRec)
449 + {
450 + string file = (string) fileRec[1];
451 + string comp = (string) fileRec[2];
452 + string fileName = (string) fileRec[3];
453 +
454 + InstallPath compPath = (InstallPath) componentPathMap[comp];
455 + if(compPath != null)
456 + {
457 + InstallPath filePath = new InstallPath(fileName, useShortNames);
458 + compPath.ChildPaths.Add(filePath);
459 + filePathMap[file] = filePath;
460 + }
461 + }
462 + }
463 + }
464 +
465 + return filePathMap;
466 + }
467 +
468 + /// <summary>
469 + /// Builds a mapping from Component keys to installation paths.
470 + /// </summary>
471 + /// <param name="db">Installation database.</param>
472 + /// <param name="directoryPathMap">Directory mapping returned by
473 + /// <see cref="BuildDirectoryPathMap(Database,bool)"/>.</param>
474 + /// <returns>An InstallPathMap with the described mapping.</returns>
475 + public static InstallPathMap BuildComponentPathMap(Database db, InstallPathMap directoryPathMap)
476 + {
477 + if(db == null)
478 + {
479 + throw new ArgumentNullException("db");
480 + }
481 +
482 + InstallPathMap compPathMap = new InstallPathMap();
483 +
484 + using (View compView = db.OpenView("SELECT `Component`, `Directory_` FROM `Component`"))
485 + {
486 + compView.Execute();
487 +
488 + foreach (Record compRec in compView)
489 + {
490 + using (compRec)
491 + {
492 + string comp = (string) compRec[1];
493 + InstallPath dirPath = (InstallPath) directoryPathMap[(string) compRec[2]];
494 +
495 + if (dirPath != null)
496 + {
497 + compPathMap[comp] = dirPath;
498 + }
499 + }
500 + }
501 + }
502 +
503 + return compPathMap;
504 + }
505 +
506 + /// <summary>
507 + /// Builds a mapping from Directory keys to installation paths.
508 + /// </summary>
509 + /// <param name="db">Installation database.</param>
510 + /// <param name="useShortNames">true to use short directory names; false to use long names</param>
511 + /// <returns>An InstallPathMap with the described mapping.</returns>
512 + public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames)
513 + {
514 + return BuildDirectoryPathMap(db, useShortNames, null, null);
515 + }
516 +
517 + /// <summary>
518 + /// Builds a mapping of Directory keys to directory paths, specifying root directories
519 + /// for the source and target paths.
520 + /// </summary>
521 + /// <param name="db">Database containing the Directory table.</param>
522 + /// <param name="useShortNames">true to use short directory names; false to use long names</param>
523 + /// <param name="sourceRootDir">The root directory path of all source paths, or null to leave them relative.</param>
524 + /// <param name="targetRootDir">The root directory path of all source paths, or null to leave them relative.</param>
525 + /// <returns>An InstallPathMap with the described mapping.</returns>
526 + public static InstallPathMap BuildDirectoryPathMap(Database db, bool useShortNames,
527 + string sourceRootDir, string targetRootDir)
528 + {
529 + if(db == null)
530 + {
531 + throw new ArgumentNullException("db");
532 + }
533 +
534 + if(sourceRootDir == null) sourceRootDir = "";
535 + if(targetRootDir == null) targetRootDir = "";
536 +
537 + InstallPathMap dirMap = new InstallPathMap();
538 + IDictionary dirTreeMap = new Hashtable();
539 +
540 + using (View dirView = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
541 + {
542 + dirView.Execute();
543 +
544 + foreach (Record dirRec in dirView) using (dirRec)
545 + {
546 + string key = (string) dirRec[1];
547 + string parentKey = (string) dirRec[2];
548 + InstallPath dir = new InstallPath((string) dirRec[3], useShortNames);
549 +
550 + dirMap[key] = dir;
551 +
552 + InstallPathMap siblingDirs = (InstallPathMap) dirTreeMap[parentKey];
553 + if (siblingDirs == null)
554 + {
555 + siblingDirs = new InstallPathMap();
556 + dirTreeMap[parentKey] = siblingDirs;
557 + }
558 + siblingDirs.Add(key, dir);
559 + }
560 + }
561 +
562 + foreach (KeyValuePair<string, InstallPath> entry in (InstallPathMap) dirTreeMap[""])
563 + {
564 + string key = (string) entry.Key;
565 + InstallPath dir = (InstallPath) entry.Value;
566 + LinkSubdirectories(key, dir, dirTreeMap);
567 + }
568 +
569 + InstallPath targetDirPath = (InstallPath) dirMap["TARGETDIR"];
570 + if(targetDirPath != null)
571 + {
572 + targetDirPath.SourcePath = sourceRootDir;
573 + targetDirPath.TargetPath = targetRootDir;
574 + }
575 +
576 + return dirMap;
577 + }
578 +
579 + private static void LinkSubdirectories(string key, InstallPath dir, IDictionary dirTreeMap)
580 + {
581 + InstallPathMap subDirs = (InstallPathMap) dirTreeMap[key];
582 + if(subDirs != null)
583 + {
584 + foreach (KeyValuePair<string, InstallPath> entry in subDirs)
585 + {
586 + string subKey = (string) entry.Key;
587 + InstallPath subDir = (InstallPath) entry.Value;
588 + dir.ChildPaths.Add(subDir);
589 + LinkSubdirectories(subKey, subDir, dirTreeMap);
590 + }
591 + }
592 + }
593 +
594 + private Dictionary<string, InstallPath> items;
595 +
596 + /// <summary>
597 + /// Creates a new empty InstallPathMap.
598 + /// </summary>
599 + public InstallPathMap()
600 + {
601 + this.items = new Dictionary<string,InstallPath>(StringComparer.Ordinal);
602 + }
603 +
604 + /// <summary>
605 + /// Gets a mapping from keys to source paths.
606 + /// </summary>
607 + public IDictionary<string, string> SourcePaths
608 + {
609 + get
610 + {
611 + return new SourcePathMap(this);
612 + }
613 + }
614 +
615 + /// <summary>
616 + /// Gets a mapping from keys to target paths.
617 + /// </summary>
618 + public IDictionary<string, string> TargetPaths
619 + {
620 + get
621 + {
622 + return new TargetPathMap(this);
623 + }
624 + }
625 +
626 + /// <summary>
627 + /// Gets or sets an install path for a direcotry, component, or file key.
628 + /// </summary>
629 + /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
630 + /// either the Directory, Component, or File table.</param>
631 + /// <remarks>
632 + /// Changing an install path does not modify the Database used to generate this InstallPathMap.
633 + /// </remarks>
634 + public InstallPath this[string key]
635 + {
636 + get
637 + {
638 + InstallPath value = null;
639 + this.items.TryGetValue(key, out value);
640 + return value;
641 + }
642 + set
643 + {
644 + this.items[key] = value;
645 + }
646 + }
647 +
648 + /// <summary>
649 + /// Gets the collection of keys in the InstallPathMap. Depending on the type of InstallPathMap,
650 + /// they are all directory, component, or file key strings.
651 + /// </summary>
652 + public ICollection<string> Keys
653 + {
654 + get
655 + {
656 + return this.items.Keys;
657 + }
658 + }
659 +
660 + /// <summary>
661 + /// Gets the collection of InstallPath values in the InstallPathMap.
662 + /// </summary>
663 + public ICollection<InstallPath> Values
664 + {
665 + get
666 + {
667 + return this.items.Values;
668 + }
669 + }
670 +
671 + /// <summary>
672 + /// Sets an install path for a direcotry, component, or file key.
673 + /// </summary>
674 + /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
675 + /// either the Directory, Component, or File table.</param>
676 + /// <param name="installPath">The install path of the key item.</param>
677 + /// <remarks>
678 + /// Changing an install path does not modify the Database used to generate this InstallPathMap.
679 + /// </remarks>
680 + public void Add(string key, InstallPath installPath)
681 + {
682 + this.items.Add(key, installPath);
683 + }
684 +
685 + /// <summary>
686 + /// Removes an install path from the map.
687 + /// </summary>
688 + /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
689 + /// either the Directory, Component, or File table.</param>
690 + /// <returns>true if the item was removed, false if it did not exist</returns>
691 + /// <remarks>
692 + /// Changing an install path does not modify the Database used to generate this InstallPathMap.
693 + /// </remarks>
694 + public bool Remove(string key)
695 + {
696 + return this.items.Remove(key);
697 + }
698 +
699 + /// <summary>
700 + /// Tests whether a direcotry, component, or file key exists in the map.
701 + /// </summary>
702 + /// <param name="key">Depending on the type of InstallPathMap, this is the primary key from the
703 + /// either the Directory, Component, or File table.</param>
704 + /// <returns>true if the key is found; false otherwise</returns>
705 + public bool ContainsKey(string key)
706 + {
707 + return this.items.ContainsKey(key);
708 + }
709 +
710 + /*
711 + public override string ToString()
712 + {
713 + System.Text.StringBuilder buf = new System.Text.StringBuilder();
714 + foreach(KeyValuePair<string, InstallPath> entry in this)
715 + {
716 + buf.AppendFormat("{0}={1}", entry.Key, entry.Value);
717 + buf.Append("\n");
718 + }
719 + return buf.ToString();
720 + }
721 + */
722 +
723 + /// <summary>
724 + /// Attempts to get a value from the dictionary.
725 + /// </summary>
726 + /// <param name="key">The key to lookup.</param>
727 + /// <param name="value">Receives the value, or null if they key was not found.</param>
728 + /// <returns>True if the value was found, else false.</returns>
729 + public bool TryGetValue(string key, out InstallPath value)
730 + {
731 + return this.items.TryGetValue(key, out value);
732 + }
733 +
734 + void ICollection<KeyValuePair<string, InstallPath>>.Add(KeyValuePair<string, InstallPath> item)
735 + {
736 + ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Add(item);
737 + }
738 +
739 + /// <summary>
740 + /// Removes all entries from the dictionary.
741 + /// </summary>
742 + public void Clear()
743 + {
744 + this.items.Clear();
745 + }
746 +
747 + bool ICollection<KeyValuePair<string, InstallPath>>.Contains(KeyValuePair<string, InstallPath> item)
748 + {
749 + return ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Contains(item);
750 + }
751 +
752 + void ICollection<KeyValuePair<string, InstallPath>>.CopyTo(KeyValuePair<string, InstallPath>[] array, int arrayIndex)
753 + {
754 + ((ICollection<KeyValuePair<string, InstallPath>>) this.items).CopyTo(array, arrayIndex);
755 + }
756 +
757 + /// <summary>
758 + /// Gets the number of entries in the dictionary.
759 + /// </summary>
760 + public int Count
761 + {
762 + get
763 + {
764 + return this.items.Count;
765 + }
766 + }
767 +
768 + bool ICollection<KeyValuePair<string, InstallPath>>.IsReadOnly
769 + {
770 + get
771 + {
772 + return false;
773 + }
774 + }
775 +
776 + bool ICollection<KeyValuePair<string, InstallPath>>.Remove(KeyValuePair<string, InstallPath> item)
777 + {
778 + return ((ICollection<KeyValuePair<string, InstallPath>>) this.items).Remove(item);
779 + }
780 +
781 + /// <summary>
782 + /// Gets an enumerator over all entries in the dictionary.
783 + /// </summary>
784 + /// <returns>An enumerator for the dictionary.</returns>
785 + public IEnumerator<KeyValuePair<string, InstallPath>> GetEnumerator()
786 + {
787 + return this.items.GetEnumerator();
788 + }
789 +
790 + IEnumerator IEnumerable.GetEnumerator()
791 + {
792 + return this.items.GetEnumerator();
793 + }
794 + }
795 +
796 + internal class SourcePathMap : IDictionary<string, string>
797 + {
798 + private const string RO_MSG =
799 + "The SourcePathMap collection is read-only. " +
800 + "Modify the InstallPathMap instead.";
801 +
802 + private InstallPathMap map;
803 +
804 + internal SourcePathMap(InstallPathMap map)
805 + {
806 + this.map = map;
807 + }
808 +
809 + public void Add(string key, string value)
810 + {
811 + throw new InvalidOperationException(RO_MSG);
812 + }
813 +
814 + public bool ContainsKey(string key)
815 + {
816 + return this.map.ContainsKey(key);
817 + }
818 +
819 + public ICollection<string> Keys
820 + {
821 + get
822 + {
823 + return this.map.Keys;
824 + }
825 + }
826 +
827 + public bool Remove(string key)
828 + {
829 + throw new InvalidOperationException(RO_MSG);
830 + }
831 +
832 + public bool TryGetValue(string key, out string value)
833 + {
834 + InstallPath installPath;
835 + if (this.map.TryGetValue(key, out installPath))
836 + {
837 + value = installPath.SourcePath;
838 + return true;
839 + }
840 + else
841 + {
842 + value = null;
843 + return false;
844 + }
845 + }
846 +
847 + public ICollection<string> Values
848 + {
849 + get
850 + {
851 + List<string> values = new List<string>(this.Count);
852 + foreach (KeyValuePair<string, InstallPath> entry in this.map)
853 + {
854 + values.Add(entry.Value.SourcePath);
855 + }
856 + return values;
857 + }
858 + }
859 +
860 + public string this[string key]
861 + {
862 + get
863 + {
864 + string value = null;
865 + this.TryGetValue(key, out value);
866 + return value;
867 + }
868 + set
869 + {
870 + throw new InvalidOperationException(RO_MSG);
871 + }
872 + }
873 +
874 + public void Add(KeyValuePair<string, string> item)
875 + {
876 + throw new InvalidOperationException(RO_MSG);
877 + }
878 +
879 + public void Clear()
880 + {
881 + throw new InvalidOperationException(RO_MSG);
882 + }
883 +
884 + public bool Contains(KeyValuePair<string, string> item)
885 + {
886 + string value = this[item.Key];
887 + return value == item.Value;
888 + }
889 +
890 + public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
891 + {
892 + foreach (KeyValuePair<string, string> entry in this)
893 + {
894 + array[arrayIndex] = entry;
895 + arrayIndex++;
896 + }
897 + }
898 +
899 + public int Count
900 + {
901 + get
902 + {
903 + return this.map.Count;
904 + }
905 + }
906 +
907 + public bool IsReadOnly
908 + {
909 + get
910 + {
911 + return true;
912 + }
913 + }
914 +
915 + public bool Remove(KeyValuePair<string, string> item)
916 + {
917 + throw new InvalidOperationException(RO_MSG);
918 + }
919 +
920 + public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
921 + {
922 + foreach (KeyValuePair<string, InstallPath> entry in this.map)
923 + {
924 + yield return new KeyValuePair<string, string>(
925 + entry.Key, entry.Value.SourcePath);
926 + }
927 + }
928 +
929 + IEnumerator IEnumerable.GetEnumerator()
930 + {
931 + return this.GetEnumerator();
932 + }
933 + }
934 +
935 + internal class TargetPathMap : IDictionary<string, string>
936 + {
937 + private const string RO_MSG =
938 + "The TargetPathMap collection is read-only. " +
939 + "Modify the InstallPathMap instead.";
940 +
941 + private InstallPathMap map;
942 +
943 + internal TargetPathMap(InstallPathMap map)
944 + {
945 + this.map = map;
946 + }
947 +
948 + public void Add(string key, string value)
949 + {
950 + throw new InvalidOperationException(RO_MSG);
951 + }
952 +
953 + public bool ContainsKey(string key)
954 + {
955 + return this.map.ContainsKey(key);
956 + }
957 +
958 + public ICollection<string> Keys
959 + {
960 + get
961 + {
962 + return this.map.Keys;
963 + }
964 + }
965 +
966 + public bool Remove(string key)
967 + {
968 + throw new InvalidOperationException(RO_MSG);
969 + }
970 +
971 + public bool TryGetValue(string key, out string value)
972 + {
973 + InstallPath installPath;
974 + if (this.map.TryGetValue(key, out installPath))
975 + {
976 + value = installPath.TargetPath;
977 + return true;
978 + }
979 + else
980 + {
981 + value = null;
982 + return false;
983 + }
984 + }
985 +
986 + public ICollection<string> Values
987 + {
988 + get
989 + {
990 + List<string> values = new List<string>(this.Count);
991 + foreach (KeyValuePair<string, InstallPath> entry in this.map)
992 + {
993 + values.Add(entry.Value.TargetPath);
994 + }
995 + return values;
996 + }
997 + }
998 +
999 + public string this[string key]
1000 + {
1001 + get
1002 + {
1003 + string value = null;
1004 + this.TryGetValue(key, out value);
1005 + return value;
1006 + }
1007 + set
1008 + {
1009 + throw new InvalidOperationException(RO_MSG);
1010 + }
1011 + }
1012 +
1013 + public void Add(KeyValuePair<string, string> item)
1014 + {
1015 + throw new InvalidOperationException(RO_MSG);
1016 + }
1017 +
1018 + public void Clear()
1019 + {
1020 + throw new InvalidOperationException(RO_MSG);
1021 + }
1022 +
1023 + public bool Contains(KeyValuePair<string, string> item)
1024 + {
1025 + string value = this[item.Key];
1026 + return value == item.Value;
1027 + }
1028 +
1029 + public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
1030 + {
1031 + foreach (KeyValuePair<string, string> entry in this)
1032 + {
1033 + array[arrayIndex] = entry;
1034 + arrayIndex++;
1035 + }
1036 + }
1037 +
1038 + public int Count
1039 + {
1040 + get
1041 + {
1042 + return this.map.Count;
1043 + }
1044 + }
1045 +
1046 + public bool IsReadOnly
1047 + {
1048 + get
1049 + {
1050 + return true;
1051 + }
1052 + }
1053 +
1054 + public bool Remove(KeyValuePair<string, string> item)
1055 + {
1056 + throw new InvalidOperationException(RO_MSG);
1057 + }
1058 +
1059 + public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
1060 + {
1061 + foreach (KeyValuePair<string, InstallPath> entry in this.map)
1062 + {
1063 + yield return new KeyValuePair<string, string>(
1064 + entry.Key, entry.Value.TargetPath);
1065 + }
1066 + }
1067 +
1068 + IEnumerator IEnumerable.GetEnumerator()
1069 + {
1070 + return this.GetEnumerator();
1071 + }
1072 + }
1073 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Package/PatchPackage.cs new
+259
@@ -0,0 +1,259 @@
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 +namespace WixToolset.Dtf.WindowsInstaller.Package
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections;
9 + using System.Globalization;
10 + using System.Runtime.InteropServices;
11 +
12 + /// <summary>
13 + /// Provides access to convenient properties and operations on a patch package (.MSP).
14 + /// </summary>
15 + public class PatchPackage : Database
16 + {
17 + /// <summary>
18 + /// Creates a new patch package object; opening the patch database in read-only mode.
19 + /// </summary>
20 + /// <param name="packagePath">Path to the patch package (.MSP)</param>
21 + /// <remarks>The PatchPackage object only opens the patch database in read-only mode, because
22 + /// transforms (sub-storages) cannot be read if the database is open in read-write mode.</remarks>
23 + public PatchPackage(string packagePath)
24 + : base(packagePath, (DatabaseOpenMode) ((int) DatabaseOpenMode.ReadOnly | 32))
25 + // TODO: figure out what to do about DatabaseOpenMode.Patch
26 + {
27 + }
28 +
29 + /// <summary>
30 + /// Handle this event to receive status messages when operations are performed on the patch package.
31 + /// </summary>
32 + /// <example>
33 + /// <c>patchPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
34 + /// </example>
35 + public event InstallPackageMessageHandler Message;
36 +
37 + /// <summary>
38 + /// Sends a message to the <see cref="Message"/> event-handler.
39 + /// </summary>
40 + /// <param name="format">Message string, containing 0 or more format items</param>
41 + /// <param name="args">Items to be formatted</param>
42 + protected void LogMessage(string format, params object[] args)
43 + {
44 + if(this.Message != null)
45 + {
46 + this.Message(format, args);
47 + }
48 + }
49 +
50 + /// <summary>
51 + /// Gets the patch code (GUID) of the patch package.
52 + /// </summary>
53 + /// <remarks>
54 + /// The patch code is stored in the RevisionNumber field of the patch summary information.
55 + /// </remarks>
56 + public string PatchCode
57 + {
58 + get
59 + {
60 + string guids = this.SummaryInfo.RevisionNumber;
61 + return guids.Substring(0, guids.IndexOf('}') + 1);
62 + }
63 + }
64 +
65 + /// <summary>
66 + /// Gets the list of patch codes that are replaced by this patch package.
67 + /// </summary>
68 + /// <returns>Array of replaced patch codes (GUIDs)</returns>
69 + /// <remarks>
70 + /// The list of replaced patch codes is stored in the RevisionNumber field of the patch summary information.
71 + /// </remarks>
72 + public string[] GetReplacedPatchCodes()
73 + {
74 + ArrayList patchCodeList = new ArrayList();
75 + string guids = this.SummaryInfo.RevisionNumber;
76 + int thisGuid = guids.IndexOf('}') + 1;
77 + int nextGuid = guids.IndexOf('}', thisGuid) + 1;
78 + while(nextGuid > 0)
79 + {
80 + patchCodeList.Add(guids.Substring(thisGuid, (nextGuid - thisGuid)));
81 + thisGuid = nextGuid;
82 + nextGuid = guids.IndexOf('}', thisGuid) + 1;
83 + }
84 + return (string[]) patchCodeList.ToArray(typeof(string));
85 + }
86 +
87 + /// <summary>
88 + /// Gets the list of product codes of products targeted by this patch package.
89 + /// </summary>
90 + /// <returns>Array of product codes (GUIDs)</returns>
91 + /// <remarks>
92 + /// The list of target product codes is stored in the Template field of the patch summary information.
93 + /// </remarks>
94 + public string[] GetTargetProductCodes()
95 + {
96 + string productList = this.SummaryInfo.Template;
97 + return productList.Split(';');
98 + }
99 +
100 + /// <summary>
101 + /// Gets the names of the transforms included in the patch package.
102 + /// </summary>
103 + /// <returns>Array of transform names</returns>
104 + /// <remarks>
105 + /// The returned list does not include the &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;
106 + /// <p>The list of transform names is stored in the LastSavedBy field of the patch summary information.</p>
107 + /// </remarks>
108 + public string[] GetTransforms()
109 + {
110 + return this.GetTransforms(false);
111 + }
112 + /// <summary>
113 + /// Gets the names of the transforms included in the patch package.
114 + /// </summary>
115 + /// <param name="includeSpecialTransforms">Specifies whether to include the
116 + /// &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;</param>
117 + /// <returns>Array of transform names</returns>
118 + /// <remarks>
119 + /// The list of transform names is stored in the LastSavedBy field of the patch summary information.
120 + /// </remarks>
121 + public string[] GetTransforms(bool includeSpecialTransforms)
122 + {
123 + ArrayList transformArray = new ArrayList();
124 + string transformList = this.SummaryInfo.LastSavedBy;
125 + foreach(string transform in transformList.Split(';', ':'))
126 + {
127 + if(transform.Length != 0 && (includeSpecialTransforms || !transform.StartsWith("#", StringComparison.Ordinal)))
128 + {
129 + transformArray.Add(transform);
130 + }
131 + }
132 + return (string[]) transformArray.ToArray(typeof(string));
133 + }
134 +
135 + /// <summary>
136 + /// Gets information about the transforms included in the patch package.
137 + /// </summary>
138 + /// <returns>Array containing information about each transform</returns>
139 + /// <remarks>
140 + /// The returned info does not include the &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;
141 + /// </remarks>
142 + public TransformInfo[] GetTransformsInfo()
143 + {
144 + return this.GetTransformsInfo(false);
145 + }
146 +
147 + /// <summary>
148 + /// Gets information about the transforms included in the patch package.
149 + /// </summary>
150 + /// <param name="includeSpecialTransforms">Specifies whether to include the
151 + /// &quot;patch special transforms&quot; that are prefixed with &quot;#&quot;</param>
152 + /// <returns>Array containing information about each transform</returns>
153 + public TransformInfo[] GetTransformsInfo(bool includeSpecialTransforms)
154 + {
155 + string[] transforms = this.GetTransforms(includeSpecialTransforms);
156 + ArrayList transformInfoArray = new ArrayList(transforms.Length);
157 + foreach(string transform in transforms)
158 + {
159 + transformInfoArray.Add(this.GetTransformInfo(transform));
160 + }
161 + return (TransformInfo[]) transformInfoArray.ToArray(typeof(TransformInfo));
162 + }
163 +
164 + /// <summary>
165 + /// Gets information about a transforms included in the patch package.
166 + /// </summary>
167 + /// <param name="transform">Name of the transform to extract; this may optionally be a
168 + /// special transform prefixed by &quot;#&quot;</param>
169 + /// <returns>Information about the transform</returns>
170 + public TransformInfo GetTransformInfo(string transform)
171 + {
172 + string tempTransformFile = null;
173 + try
174 + {
175 + tempTransformFile = Path.GetTempFileName();
176 + this.ExtractTransform(transform, tempTransformFile);
177 + using(SummaryInfo transformSummInfo = new SummaryInfo(tempTransformFile, false))
178 + {
179 + return new TransformInfo(transform, transformSummInfo);
180 + }
181 + }
182 + finally
183 + {
184 + if(tempTransformFile != null && File.Exists(tempTransformFile))
185 + {
186 + File.Delete(tempTransformFile);
187 + }
188 + }
189 + }
190 +
191 + /// <summary>
192 + /// Analyzes the transforms included in the patch package to find the ones that
193 + /// are applicable to an install package.
194 + /// </summary>
195 + /// <param name="installPackage">The install package to validate the transforms against</param>
196 + /// <returns>Array of valid transform names</returns>
197 + /// <remarks>
198 + /// The returned list does not include the &quot;patch special transforms&quot; that
199 + /// are prefixed with &quot;#&quot; If a transform is valid, then its corresponding
200 + /// special transform is assumed to be valid as well.
201 + /// </remarks>
202 + public string[] GetValidTransforms(InstallPackage installPackage)
203 + {
204 + ArrayList transformArray = new ArrayList();
205 + string transformList = this.SummaryInfo.LastSavedBy;
206 + foreach(string transform in transformList.Split(';', ':'))
207 + {
208 + if(transform.Length != 0 && !transform.StartsWith("#", StringComparison.Ordinal))
209 + {
210 + this.LogMessage("Checking validity of transform {0}", transform);
211 + string tempTransformFile = null;
212 + try
213 + {
214 + tempTransformFile = Path.GetTempFileName();
215 + this.ExtractTransform(transform, tempTransformFile);
216 + if(installPackage.IsTransformValid(tempTransformFile))
217 + {
218 + this.LogMessage("Found valid transform: {0}", transform);
219 + transformArray.Add(transform);
220 + }
221 + }
222 + finally
223 + {
224 + if(tempTransformFile != null && File.Exists(tempTransformFile))
225 + {
226 + try { File.Delete(tempTransformFile); }
227 + catch(IOException) { }
228 + }
229 + }
230 + }
231 + }
232 + return (string[]) transformArray.ToArray(typeof(string));
233 + }
234 +
235 + /// <summary>
236 + /// Extracts a transform (.MST) from a patch package.
237 + /// </summary>
238 + /// <param name="transform">Name of the transform to extract; this may optionally be a
239 + /// special transform prefixed by &quot;#&quot;</param>
240 + /// <param name="extractFile">Location where the transform will be extracted</param>
241 + public void ExtractTransform(string transform, string extractFile)
242 + {
243 + using(View stgView = this.OpenView("SELECT `Name`, `Data` FROM `_Storages` WHERE `Name` = '{0}'", transform))
244 + {
245 + stgView.Execute();
246 + Record stgRec = stgView.Fetch();
247 + if(stgRec == null)
248 + {
249 + this.LogMessage("Transform not found: {0}", transform);
250 + throw new InstallerException("Transform not found: " + transform);
251 + }
252 + using(stgRec)
253 + {
254 + stgRec.GetStream("Data", extractFile);
255 + }
256 + }
257 + }
258 + }
259 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Package/TransformInfo.cs new
+154
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller.Package
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Globalization;
8 +
9 + /// <summary>
10 + /// Contains properties of a transform package (.MST).
11 + /// </summary>
12 + public class TransformInfo
13 + {
14 + /// <summary>
15 + /// Reads transform information from a transform package.
16 + /// </summary>
17 + /// <param name="mstFile">Path to a transform package (.MST file).</param>
18 + public TransformInfo(string mstFile)
19 + {
20 + this.name = Path.GetFileName(mstFile);
21 + using (SummaryInfo transformSummInfo = new SummaryInfo(mstFile, false))
22 + {
23 + this.DecodeSummaryInfo(transformSummInfo);
24 + }
25 + }
26 +
27 + /// <summary>
28 + /// Reads transform information from the summary information of a transform package.
29 + /// </summary>
30 + /// <param name="name">Filename of the transform (optional).</param>
31 + /// <param name="transformSummaryInfo">Handle to the summary information of a transform package (.MST file).</param>
32 + public TransformInfo(string name, SummaryInfo transformSummaryInfo)
33 + {
34 + this.name = name;
35 + this.DecodeSummaryInfo(transformSummaryInfo);
36 + }
37 +
38 + private void DecodeSummaryInfo(SummaryInfo transformSummaryInfo)
39 + {
40 + try
41 + {
42 + string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3);
43 + this.targetProductCode = rev[0].Substring(0, 38);
44 + this.targetProductVersion = rev[0].Substring(38);
45 + this.upgradeProductCode = rev[1].Substring(0, 38);
46 + this.upgradeProductVersion = rev[1].Substring(38);
47 + this.upgradeCode = rev[2];
48 +
49 + string[] templ = transformSummaryInfo.Template.Split(new Char[] { ';' }, 2);
50 + this.targetPlatform = templ[0];
51 + this.targetLanguage = 0;
52 + if (templ.Length >= 2 && templ[1].Length > 0)
53 + {
54 + this.targetLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat);
55 + }
56 +
57 + this.validateFlags = (TransformValidations) transformSummaryInfo.CharacterCount;
58 + }
59 + catch (Exception ex)
60 + {
61 + throw new InstallerException("Invalid transform summary info", ex);
62 + }
63 + }
64 +
65 + /// <summary>
66 + /// Gets the filename of the transform.
67 + /// </summary>
68 + public string Name
69 + {
70 + get { return this.name; }
71 + }
72 + private string name;
73 +
74 + /// <summary>
75 + /// Gets the target product code of the transform.
76 + /// </summary>
77 + public string TargetProductCode
78 + {
79 + get { return this.targetProductCode; }
80 + }
81 + private string targetProductCode;
82 +
83 + /// <summary>
84 + /// Gets the target product version of the transform.
85 + /// </summary>
86 + public string TargetProductVersion
87 + {
88 + get { return this.targetProductVersion; }
89 + }
90 + private string targetProductVersion;
91 +
92 + /// <summary>
93 + /// Gets the upgrade product code of the transform.
94 + /// </summary>
95 + public string UpgradeProductCode
96 + {
97 + get { return this.upgradeProductCode; }
98 + }
99 + private string upgradeProductCode;
100 +
101 + /// <summary>
102 + /// Gets the upgrade product version of the transform.
103 + /// </summary>
104 + public string UpgradeProductVersion
105 + {
106 + get { return this.upgradeProductVersion; }
107 + }
108 + private string upgradeProductVersion;
109 +
110 + /// <summary>
111 + /// Gets the upgrade code of the transform.
112 + /// </summary>
113 + public string UpgradeCode
114 + {
115 + get { return this.upgradeCode; }
116 + }
117 + private string upgradeCode;
118 +
119 + /// <summary>
120 + /// Gets the target platform of the transform.
121 + /// </summary>
122 + public string TargetPlatform
123 + {
124 + get { return this.targetPlatform; }
125 + }
126 + private string targetPlatform;
127 +
128 + /// <summary>
129 + /// Gets the target language of the transform, or 0 if the transform is language-neutral.
130 + /// </summary>
131 + public int TargetLanguage
132 + {
133 + get { return this.targetLanguage; }
134 + }
135 + private int targetLanguage;
136 +
137 + /// <summary>
138 + /// Gets the validation flags specified when the transform was generated.
139 + /// </summary>
140 + public TransformValidations Validations
141 + {
142 + get { return this.validateFlags; }
143 + }
144 + private TransformValidations validateFlags;
145 +
146 + /// <summary>
147 + /// Returns the name of the transform.
148 + /// </summary>
149 + public override string ToString()
150 + {
151 + return (this.Name != null ? this.Name : "MST");
152 + }
153 + }
154 +}
src/dtf/WixToolset.Dtf.WindowsInstaller.Package/WixToolset.Dtf.WindowsInstaller.Package.csproj new
+23
@@ -0,0 +1,23 @@
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 + <RootNamespace>WixToolset.Dtf.WindowsInstaller</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.WindowsInstaller.Package</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Extended managed libraries for Windows Installer</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
15 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
16 + <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
17 + </ItemGroup>
18 +
19 + <ItemGroup>
20 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
21 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
22 + </ItemGroup>
23 +</Project>
src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnCollection.cs new
+333
@@ -0,0 +1,333 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 + using System.Text;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + /// <summary>
13 + /// Collection of column information related to a <see cref="TableInfo"/> or
14 + /// <see cref="View"/>.
15 + /// </summary>
16 + public sealed class ColumnCollection : ICollection<ColumnInfo>
17 + {
18 + private IList<ColumnInfo> columns;
19 + private string formatString;
20 +
21 + /// <summary>
22 + /// Creates a new ColumnCollection based on a specified list of columns.
23 + /// </summary>
24 + /// <param name="columns">columns to be added to the new collection</param>
25 + public ColumnCollection(ICollection<ColumnInfo> columns)
26 + {
27 + if (columns == null)
28 + {
29 + throw new ArgumentNullException("columns");
30 + }
31 +
32 + this.columns = new List<ColumnInfo>(columns);
33 + }
34 +
35 + /// <summary>
36 + /// Creates a new ColumnCollection that is associated with a database table.
37 + /// </summary>
38 + /// <param name="view">view that contains the columns</param>
39 + internal ColumnCollection(View view)
40 + {
41 + if (view == null)
42 + {
43 + throw new ArgumentNullException("view");
44 + }
45 +
46 + this.columns = ColumnCollection.GetViewColumns(view);
47 + }
48 +
49 + /// <summary>
50 + /// Gets the number of columns in the collection.
51 + /// </summary>
52 + /// <value>number of columns in the collection</value>
53 + public int Count
54 + {
55 + get
56 + {
57 + return this.columns.Count;
58 + }
59 + }
60 +
61 + /// <summary>
62 + /// Gets a boolean value indicating whether the collection is read-only.
63 + /// A ColumnCollection is read-only if it is associated with a <see cref="View"/>
64 + /// or a read-only <see cref="Database"/>.
65 + /// </summary>
66 + /// <value>read-only status of the collection</value>
67 + public bool IsReadOnly
68 + {
69 + get
70 + {
71 + return true;
72 + }
73 + }
74 +
75 + /// <summary>
76 + /// Gets information about a specific column in the collection.
77 + /// </summary>
78 + /// <param name="columnIndex">1-based index into the column collection</param>
79 + /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnIndex"/> is less
80 + /// than 1 or greater than the number of columns in the collection</exception>
81 + public ColumnInfo this[int columnIndex]
82 + {
83 + get
84 + {
85 + if (columnIndex >= 0 && columnIndex < this.columns.Count)
86 + {
87 + return this.columns[columnIndex];
88 + }
89 + else
90 + {
91 + throw new ArgumentOutOfRangeException("columnIndex");
92 + }
93 + }
94 + }
95 +
96 + /// <summary>
97 + /// Gets information about a specific column in the collection.
98 + /// </summary>
99 + /// <param name="columnName">case-sensitive name of a column collection</param>
100 + /// <exception cref="ArgumentOutOfRangeException"><paramref name="columnName"/> does
101 + /// not exist in the collection</exception>
102 + public ColumnInfo this[string columnName]
103 + {
104 + get
105 + {
106 + if (String.IsNullOrEmpty(columnName))
107 + {
108 + throw new ArgumentNullException("columnName");
109 + }
110 +
111 + foreach (ColumnInfo colInfo in this.columns)
112 + {
113 + if (colInfo.Name == columnName)
114 + {
115 + return colInfo;
116 + }
117 + }
118 +
119 + throw new ArgumentOutOfRangeException("columnName");
120 + }
121 + }
122 +
123 + /// <summary>
124 + /// Not supported because the collection is read-only.
125 + /// </summary>
126 + /// <param name="item">information about the column being added</param>
127 + /// <exception cref="InvalidOperationException">the collection is read-only</exception>
128 + public void Add(ColumnInfo item)
129 + {
130 + throw new InvalidOperationException();
131 + }
132 +
133 + /// <summary>
134 + /// Not supported because the collection is read-only.
135 + /// </summary>
136 + /// <exception cref="InvalidOperationException">the collection is read-only</exception>
137 + public void Clear()
138 + {
139 + throw new InvalidOperationException();
140 + }
141 +
142 + /// <summary>
143 + /// Checks if a column with a given name exists in the collection.
144 + /// </summary>
145 + /// <param name="columnName">case-sensitive name of the column to look for</param>
146 + /// <returns>true if the column exists in the collection, false otherwise</returns>
147 + public bool Contains(string columnName)
148 + {
149 + return this.IndexOf(columnName) >= 0;
150 + }
151 +
152 + /// <summary>
153 + /// Checks if a column with a given name exists in the collection.
154 + /// </summary>
155 + /// <param name="column">column to look for, with case-sensitive name</param>
156 + /// <returns>true if the column exists in the collection, false otherwise</returns>
157 + bool ICollection<ColumnInfo>.Contains(ColumnInfo column)
158 + {
159 + return this.Contains(column.Name);
160 + }
161 +
162 + /// <summary>
163 + /// Gets the index of a column within the collection.
164 + /// </summary>
165 + /// <param name="columnName">case-sensitive name of the column to look for</param>
166 + /// <returns>0-based index of the column, or -1 if not found</returns>
167 + public int IndexOf(string columnName)
168 + {
169 + if (String.IsNullOrEmpty(columnName))
170 + {
171 + throw new ArgumentNullException("columnName");
172 + }
173 +
174 + for (int index = 0; index < this.columns.Count; index++)
175 + {
176 + if (this.columns[index].Name == columnName)
177 + {
178 + return index;
179 + }
180 + }
181 + return -1;
182 + }
183 +
184 + /// <summary>
185 + /// Copies the columns from this collection into an array.
186 + /// </summary>
187 + /// <param name="array">destination array to be filed</param>
188 + /// <param name="arrayIndex">offset into the destination array where copying begins</param>
189 + public void CopyTo(ColumnInfo[] array, int arrayIndex)
190 + {
191 + if (array == null)
192 + {
193 + throw new ArgumentNullException("array");
194 + }
195 +
196 + this.columns.CopyTo(array, arrayIndex);
197 + }
198 +
199 + /// <summary>
200 + /// Not supported because the collection is read-only.
201 + /// </summary>
202 + /// <param name="column">column to remove</param>
203 + /// <returns>true if the column was removed, false if it was not found</returns>
204 + /// <exception cref="InvalidOperationException">the collection is read-only</exception>
205 + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "column")]
206 + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
207 + bool ICollection<ColumnInfo>.Remove(ColumnInfo column)
208 + {
209 + throw new InvalidOperationException();
210 + }
211 +
212 + /// <summary>
213 + /// Gets an enumerator over the columns in the collection.
214 + /// </summary>
215 + /// <returns>An enumerator of ColumnInfo objects.</returns>
216 + public IEnumerator<ColumnInfo> GetEnumerator()
217 + {
218 + return this.columns.GetEnumerator();
219 + }
220 +
221 + /// <summary>
222 + /// Gets a string suitable for printing all the values of a record containing these columns.
223 + /// </summary>
224 + public string FormatString
225 + {
226 + get
227 + {
228 + if (this.formatString == null)
229 + {
230 + this.formatString = CreateFormatString(this.columns);
231 + }
232 + return this.formatString;
233 + }
234 + }
235 +
236 + private static string CreateFormatString(IList<ColumnInfo> columns)
237 + {
238 + StringBuilder sb = new StringBuilder();
239 + for (int i = 0; i < columns.Count; i++)
240 + {
241 + if (columns[i].Type == typeof(Stream))
242 + {
243 + sb.AppendFormat("{0} = [Binary Data]", columns[i].Name);
244 + }
245 + else
246 + {
247 + sb.AppendFormat("{0} = [{1}]", columns[i].Name, i + 1);
248 + }
249 +
250 + if (i < columns.Count - 1)
251 + {
252 + sb.Append(", ");
253 + }
254 + }
255 + return sb.ToString();
256 + }
257 +
258 + /// <summary>
259 + /// Gets an enumerator over the columns in the collection.
260 + /// </summary>
261 + /// <returns>An enumerator of ColumnInfo objects.</returns>
262 + IEnumerator IEnumerable.GetEnumerator()
263 + {
264 + return this.GetEnumerator();
265 + }
266 +
267 + /// <summary>
268 + /// Creates ColumnInfo objects for the associated view.
269 + /// </summary>
270 + /// <returns>dynamically-generated list of columns</returns>
271 + private static IList<ColumnInfo> GetViewColumns(View view)
272 + {
273 + IList<string> columnNames = ColumnCollection.GetViewColumns(view, false);
274 + IList<string> columnTypes = ColumnCollection.GetViewColumns(view, true);
275 +
276 + int count = columnNames.Count;
277 + if (columnTypes[count - 1] == "O0")
278 + {
279 + // Weird.. the "_Tables" table returns a second column with type "O0" -- ignore it.
280 + count--;
281 + }
282 +
283 + IList<ColumnInfo> columnsList = new List<ColumnInfo>(count);
284 + for (int i = 0; i < count; i++)
285 + {
286 + columnsList.Add(new ColumnInfo(columnNames[i], columnTypes[i]));
287 + }
288 +
289 + return columnsList;
290 + }
291 +
292 + /// <summary>
293 + /// Gets a list of column names or column-definition-strings for the
294 + /// associated view.
295 + /// </summary>
296 + /// <param name="view">the view to that defines the columns</param>
297 + /// <param name="types">true to return types (column definition strings),
298 + /// false to return names</param>
299 + /// <returns>list of column names or types</returns>
300 + private static IList<string> GetViewColumns(View view, bool types)
301 + {
302 + int recordHandle;
303 + int typesFlag = types ? 1 : 0;
304 + uint ret = RemotableNativeMethods.MsiViewGetColumnInfo(
305 + (int) view.Handle, (uint) typesFlag, out recordHandle);
306 + if (ret != 0)
307 + {
308 + throw InstallerException.ExceptionFromReturnCode(ret);
309 + }
310 +
311 + using (Record rec = new Record((IntPtr) recordHandle, true, null))
312 + {
313 + int count = rec.FieldCount;
314 + IList<string> columnsList = new List<string>(count);
315 +
316 + // Since we must be getting all strings of limited length,
317 + // this code is faster than calling rec.GetString(field).
318 + for (int field = 1; field <= count; field++)
319 + {
320 + uint bufSize = 256;
321 + StringBuilder buf = new StringBuilder((int) bufSize);
322 + ret = RemotableNativeMethods.MsiRecordGetString((int) rec.Handle, (uint) field, buf, ref bufSize);
323 + if (ret != 0)
324 + {
325 + throw InstallerException.ExceptionFromReturnCode(ret);
326 + }
327 + columnsList.Add(buf.ToString());
328 + }
329 + return columnsList;
330 + }
331 + }
332 + }
333 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnEnums.cs new
+689
@@ -0,0 +1,689 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Diagnostics.CodeAnalysis;
7 +
8 + // Enumerations are in alphabetical order.
9 +
10 + /// <summary>
11 + /// Available values for the Attributes column of the Component table.
12 + /// </summary>
13 + [Flags]
14 + public enum ComponentAttributes : int
15 + {
16 + /// <summary>
17 + /// Local only - Component cannot be run from source.
18 + /// </summary>
19 + /// <remarks><p>
20 + /// Set this value for all components belonging to a feature to prevent the feature from being run-from-network or
21 + /// run-from-source. Note that if a feature has no components, the feature always shows run-from-source and
22 + /// run-from-my-computer as valid options.
23 + /// </p></remarks>
24 + None = 0x0000,
25 +
26 + /// <summary>
27 + /// Component can only be run from source.
28 + /// </summary>
29 + /// <remarks><p>
30 + /// Set this bit for all components belonging to a feature to prevent the feature from being run-from-my-computer.
31 + /// Note that if a feature has no components, the feature always shows run-from-source and run-from-my-computer
32 + /// as valid options.
33 + /// </p></remarks>
34 + SourceOnly = 0x0001,
35 +
36 + /// <summary>
37 + /// Component can run locally or from source.
38 + /// </summary>
39 + Optional = 0x0002,
40 +
41 + /// <summary>
42 + /// If this bit is set, the value in the KeyPath column is used as a key into the Registry table.
43 + /// </summary>
44 + /// <remarks><p>
45 + /// If the Value field of the corresponding record in the Registry table is null, the Name field in that record
46 + /// must not contain "+", "-", or "*". For more information, see the description of the Name field in Registry
47 + /// table.
48 + /// <p>Setting this bit is recommended for registry entries written to the HKCU hive. This ensures the installer
49 + /// writes the necessary HKCU registry entries when there are multiple users on the same machine.</p>
50 + /// </p></remarks>
51 + RegistryKeyPath = 0x0004,
52 +
53 + /// <summary>
54 + /// If this bit is set, the installer increments the reference count in the shared DLL registry of the component's
55 + /// key file. If this bit is not set, the installer increments the reference count only if the reference count
56 + /// already exists.
57 + /// </summary>
58 + SharedDllRefCount = 0x0008,
59 +
60 + /// <summary>
61 + /// If this bit is set, the installer does not remove the component during an uninstall. The installer registers
62 + /// an extra system client for the component in the Windows Installer registry settings.
63 + /// </summary>
64 + Permanent = 0x0010,
65 +
66 + /// <summary>
67 + /// If this bit is set, the value in the KeyPath column is a key into the ODBCDataSource table.
68 + /// </summary>
69 + OdbcDataSource = 0x0020,
70 +
71 + /// <summary>
72 + /// If this bit is set, the installer reevaluates the value of the statement in the Condition column upon a reinstall.
73 + /// If the value was previously False and has changed to true, the installer installs the component. If the value
74 + /// was previously true and has changed to false, the installer removes the component even if the component has
75 + /// other products as clients.
76 + /// </summary>
77 + Transitive = 0x0040,
78 +
79 + /// <summary>
80 + /// If this bit is set, the installer does not install or reinstall the component if a key path file or a key path
81 + /// registry entry for the component already exists. The application does register itself as a client of the component.
82 + /// </summary>
83 + /// <remarks><p>
84 + /// Use this flag only for components that are being registered by the Registry table. Do not use this flag for
85 + /// components registered by the AppId, Class, Extension, ProgId, MIME, and Verb tables.
86 + /// </p></remarks>
87 + NeverOverwrite = 0x0080,
88 +
89 + /// <summary>
90 + /// Set this bit to mark this as a 64-bit component. This attribute facilitates the installation of packages that
91 + /// include both 32-bit and 64-bit components. If this bit is not set, the component is registered as a 32-bit component.
92 + /// </summary>
93 + /// <remarks><p>
94 + /// If this is a 64-bit component replacing a 32-bit component, set this bit and assign a new GUID in the
95 + /// ComponentId column.
96 + /// </p></remarks>
97 + SixtyFourBit = 0x0100,
98 +
99 + /// <summary>
100 + /// Set this bit to disable registry reflection on all existing and new registry keys affected by this component.
101 + /// </summary>
102 + /// <remarks><p>
103 + /// If this bit is set, the Windows Installer calls the RegDisableReflectionKey on each key being accessed by the component.
104 + /// This bit is available with Windows Installer version 4.0 and is ignored on 32-bit systems.
105 + /// </p></remarks>
106 + DisableRegistryReflection = 0x0200,
107 +
108 + /// <summary>
109 + /// [MSI 4.5] Set this bit for a component in a patch package to prevent leaving orphan components on the computer.
110 + /// </summary>
111 + /// <remarks><p>
112 + /// If a subsequent patch is installed, marked with the SupersedeEarlier flag in its MsiPatchSequence
113 + /// table to supersede the first patch, Windows Installer 4.5 can unregister and uninstall components marked with the
114 + /// UninstallOnSupersedence value. If the component is not marked with this bit, installation of a superseding patch can leave
115 + /// behind an unused component on the computer.
116 + /// </p></remarks>
117 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Supersedence")]
118 + UninstallOnSupersedence = 0x0400,
119 +
120 + /// <summary>
121 + /// [MSI 4.5] If a component is marked with this attribute value in at least one package installed on the system,
122 + /// the installer treats the component as marked in all packages. If a package that shares the marked component
123 + /// is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system,
124 + /// even if that highest version was installed by the package that is being uninstalled.
125 + /// </summary>
126 + Shared = 0x0800,
127 + }
128 +
129 + /// <summary>
130 + /// Defines flags for the Attributes column of the Control table.
131 + /// </summary>
132 + [Flags]
133 + public enum ControlAttributes : int
134 + {
135 + /// <summary>If this bit is set, the control is visible on the dialog box.</summary>
136 + Visible = 0x00000001,
137 +
138 + /// <summary>specifies if the given control is enabled or disabled. Most controls appear gray when disabled.</summary>
139 + Enabled = 0x00000002,
140 +
141 + /// <summary>If this bit is set, the control is displayed with a sunken, three dimensional look.</summary>
142 + Sunken = 0x00000004,
143 +
144 + /// <summary>The Indirect control attribute specifies whether the value displayed or changed by this control is referenced indirectly.</summary>
145 + Indirect = 0x00000008,
146 +
147 + /// <summary>If this bit is set on a control, the associated property specified in the Property column of the Control table is an integer.</summary>
148 + Integer = 0x00000010,
149 +
150 + /// <summary>If this bit is set the text in the control is displayed in a right-to-left reading order.</summary>
151 + RightToLeftReadingOrder = 0x00000020,
152 +
153 + /// <summary>If this style bit is set, text in the control is aligned to the right.</summary>
154 + RightAligned = 0x00000040,
155 +
156 + /// <summary>If this bit is set, the scroll bar is located on the left side of the control, otherwise it is on the right.</summary>
157 + LeftScroll = 0x00000080,
158 +
159 + /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and LeftScroll attributes.</summary>
160 + Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
161 +
162 + /// <summary>If this bit is set on a text control, the control is displayed transparently with the background showing through the control where there are no characters.</summary>
163 + Transparent = 0x00010000,
164 +
165 + /// <summary>If this bit is set on a text control, the occurrence of the character "&amp;" in a text string is displayed as itself.</summary>
166 + NoPrefix = 0x00020000,
167 +
168 + /// <summary>If this bit is set the text in the control is displayed on a single line.</summary>
169 + NoWrap = 0x00040000,
170 +
171 + /// <summary>If this bit is set for a text control, the control will automatically attempt to format the displayed text as a number representing a count of bytes.</summary>
172 + FormatSize = 0x00080000,
173 +
174 + /// <summary>If this bit is set, fonts are created using the user's default UI code page. Otherwise it is created using the database code page.</summary>
175 + UsersLanguage = 0x00100000,
176 +
177 + /// <summary>If this bit is set on an Edit control, the installer creates a multiple line edit control with a vertical scroll bar.</summary>
178 + Multiline = 0x00010000,
179 +
180 + /// <summary>This attribute creates an edit control for entering passwords. The control displays each character as an asterisk (*) as they are typed into the control.</summary>
181 + PasswordInput = 0x00200000,
182 +
183 + /// <summary>If this bit is set on a ProgressBar control, the bar is drawn as a series of small rectangles in Microsoft Windows 95-style. Otherwise it is drawn as a single continuous rectangle.</summary>
184 + Progress95 = 0x00010000,
185 +
186 + /// <summary>If this bit is set, the control shows removable volumes.</summary>
187 + RemovableVolume = 0x00010000,
188 +
189 + /// <summary>If this bit is set, the control shows fixed internal hard drives.</summary>
190 + FixedVolume = 0x00020000,
191 +
192 + /// <summary>If this bit is set, the control shows remote volumes.</summary>
193 + RemoteVolume = 0x00040000,
194 +
195 + /// <summary>If this bit is set, the control shows CD-ROM volumes.</summary>
196 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cdrom")]
197 + CdromVolume = 0x00080000,
198 +
199 + /// <summary>If this bit is set, the control shows RAM disk volumes.</summary>
200 + RamDiskVolume = 0x00100000,
201 +
202 + /// <summary>If this bit is set, the control shows floppy volumes.</summary>
203 + FloppyVolume = 0x00200000,
204 +
205 + /// <summary>Specifies whether or not the rollback backup files are included in the costs displayed by the VolumeCostList control.</summary>
206 + ShowRollbackCost = 0x00400000,
207 +
208 + /// <summary>If this bit is set, the items listed in the control are displayed in a specified order. Otherwise, items are displayed in alphabetical order.</summary>
209 + Sorted = 0x00010000,
210 +
211 + /// <summary>If this bit is set on a combo box, the edit field is replaced by a static text field. This prevents a user from entering a new value and requires the user to choose only one of the predefined values.</summary>
212 + ComboList = 0x00020000,
213 +
214 + //ImageHandle = 0x00010000,
215 +
216 + /// <summary>If this bit is set on a check box or a radio button group, the button is drawn with the appearance of a push button, but its logic stays the same.</summary>
217 + PushLike = 0x00020000,
218 +
219 + /// <summary>If this bit is set, the text in the control is replaced by a bitmap image. The Text column in the Control table is a foreign key into the Binary table.</summary>
220 + Bitmap = 0x00040000,
221 +
222 + /// <summary>If this bit is set, text is replaced by an icon image and the Text column in the Control table is a foreign key into the Binary table.</summary>
223 + Icon = 0x00080000,
224 +
225 + /// <summary>If this bit is set, the picture is cropped or centered in the control without changing its shape or size.</summary>
226 + FixedSize = 0x00100000,
227 +
228 + /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
229 + IconSize16 = 0x00200000,
230 +
231 + /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
232 + IconSize32 = 0x00400000,
233 +
234 + /// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
235 + IconSize48 = 0x00600000,
236 +
237 + /// <summary>If this bit is set, and the installation is not yet running with elevated privileges, the control is created with a UAC icon.</summary>
238 + ElevationShield = 0x00800000,
239 +
240 + /// <summary>If this bit is set, the RadioButtonGroup has text and a border displayed around it.</summary>
241 + HasBorder = 0x01000000,
242 + }
243 +
244 + /// <summary>
245 + /// Defines flags for the Type column of the CustomAction table.
246 + /// </summary>
247 + [SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags")]
248 + [Flags]
249 + public enum CustomActionTypes : int
250 + {
251 + /// <summary>Unspecified custom action type.</summary>
252 + None = 0x0000,
253 +
254 + /// <summary>Target = entry point name</summary>
255 + Dll = 0x0001,
256 +
257 + /// <summary>Target = command line args</summary>
258 + Exe = 0x0002,
259 +
260 + /// <summary>Target = text string to be formatted and set into property</summary>
261 + TextData = 0x0003,
262 +
263 + /// <summary>Target = entry point name, null if none to call</summary>
264 + JScript = 0x0005,
265 +
266 + /// <summary>Target = entry point name, null if none to call</summary>
267 + VBScript = 0x0006,
268 +
269 + /// <summary>Target = property list for nested engine initialization</summary>
270 + Install = 0x0007,
271 +
272 + /// <summary>Source = File.File, file part of installation</summary>
273 + SourceFile = 0x0010,
274 +
275 + /// <summary>Source = Directory.Directory, folder containing existing file</summary>
276 + Directory = 0x0020,
277 +
278 + /// <summary>Source = Property.Property, full path to executable</summary>
279 + Property = 0x0030,
280 +
281 + /// <summary>Ignore action return status, continue running</summary>
282 + Continue = 0x0040,
283 +
284 + /// <summary>Run asynchronously</summary>
285 + Async = 0x0080,
286 +
287 + /// <summary>Skip if UI sequence already run</summary>
288 + FirstSequence = 0x0100,
289 +
290 + /// <summary>Skip if UI sequence already run in same process</summary>
291 + OncePerProcess = 0x0200,
292 +
293 + /// <summary>Run on client only if UI already run on client</summary>
294 + ClientRepeat = 0x0300,
295 +
296 + /// <summary>Queue for execution within script</summary>
297 + InScript = 0x0400,
298 +
299 + /// <summary>In conjunction with InScript: queue in Rollback script</summary>
300 + Rollback = 0x0100,
301 +
302 + /// <summary>In conjunction with InScript: run Commit ops from script on success</summary>
303 + Commit = 0x0200,
304 +
305 + /// <summary>No impersonation, run in system context</summary>
306 + NoImpersonate = 0x0800,
307 +
308 + /// <summary>Impersonate for per-machine installs on TS machines</summary>
309 + TSAware = 0x4000,
310 +
311 + /// <summary>Script requires 64bit process</summary>
312 + SixtyFourBitScript = 0x1000,
313 +
314 + /// <summary>Don't record the contents of the Target field in the log file</summary>
315 + HideTarget = 0x2000,
316 +
317 + /// <summary>The custom action runs only when a patch is being uninstalled</summary>
318 + PatchUninstall = 0x8000,
319 + }
320 +
321 + /// <summary>
322 + /// Defines flags for the Attributes column of the Dialog table.
323 + /// </summary>
324 + [Flags]
325 + public enum DialogAttributes : int
326 + {
327 + /// <summary>If this bit is set, the dialog is originally created as visible, otherwise it is hidden.</summary>
328 + Visible = 0x00000001,
329 +
330 + /// <summary>If this bit is set, the dialog box is modal, other dialogs of the same application cannot be put on top of it, and the dialog keeps the control while it is running.</summary>
331 + Modal = 0x00000002,
332 +
333 + /// <summary>If this bit is set, the dialog box can be minimized. This bit is ignored for modal dialog boxes, which cannot be minimized.</summary>
334 + Minimize = 0x00000004,
335 +
336 + /// <summary>If this style bit is set, the dialog box will stop all other applications and no other applications can take the focus.</summary>
337 + SysModal = 0x00000008,
338 +
339 + /// <summary>If this bit is set, the other dialogs stay alive when this dialog box is created.</summary>
340 + KeepModeless = 0x00000010,
341 +
342 + /// <summary>If this bit is set, the dialog box periodically calls the installer. If the property changes, it notifies the controls on the dialog.</summary>
343 + TrackDiskSpace = 0x00000020,
344 +
345 + /// <summary>If this bit is set, the pictures on the dialog box are created with the custom palette (one per dialog received from the first control created).</summary>
346 + UseCustomPalette = 0x00000040,
347 +
348 + /// <summary>If this style bit is set the text in the dialog box is displayed in right-to-left-reading order.</summary>
349 + RightToLeftReadingOrder = 0x00000080,
350 +
351 + /// <summary>If this style bit is set, the text is aligned on the right side of the dialog box.</summary>
352 + RightAligned = 0x00000100,
353 +
354 + /// <summary>If this style bit is set, the scroll bar is located on the left side of the dialog box.</summary>
355 + LeftScroll = 0x00000200,
356 +
357 + /// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and the LeftScroll dialog style bits.</summary>
358 + Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
359 +
360 + /// <summary>If this bit is set, the dialog box is an error dialog.</summary>
361 + Error = 0x00010000,
362 + }
363 +
364 + /// <summary>
365 + /// Available values for the Attributes column of the Feature table.
366 + /// </summary>
367 + [Flags]
368 + public enum FeatureAttributes : int
369 + {
370 + /// <summary>
371 + /// Favor local - Components of this feature that are not marked for installation from source are installed locally.
372 + /// </summary>
373 + /// <remarks><p>
374 + /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
375 + /// is installed locally. Components marked <see cref="ComponentAttributes.SourceOnly"/> in the Component
376 + /// table are always run from the source CD/server. The bits FavorLocal and FavorSource work with features not
377 + /// listed by the ADVERTISE property.
378 + /// </p></remarks>
379 + None = 0x0000,
380 +
381 + /// <summary>
382 + /// Components of this feature not marked for local installation are installed to run from the source
383 + /// CD-ROM or server.
384 + /// </summary>
385 + /// <remarks><p>
386 + /// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
387 + /// is installed to run locally. Components marked <see cref="ComponentAttributes.None"/> (local-only) in the
388 + /// Component table are always installed locally. The bits FavorLocal and FavorSource work with features
389 + /// not listed by the ADVERTISE property.
390 + /// </p></remarks>
391 + FavorSource = 0x0001,
392 +
393 + /// <summary>
394 + /// Set this attribute and the state of the feature is the same as the state of the feature's parent.
395 + /// You cannot use this option if the feature is located at the root of a feature tree.
396 + /// </summary>
397 + /// <remarks><p>
398 + /// Omit this attribute and the feature state is determined according to DisallowAdvertise and
399 + /// FavorLocal and FavorSource.
400 + /// <p>To guarantee that the child feature's state always follows the state of its parent, even when the
401 + /// child and parent are initially set to absent in the SelectionTree control, you must include both
402 + /// FollowParent and UIDisallowAbsent in the attributes of the child feature.</p>
403 + /// <p>Note that if you set FollowParent without setting UIDisallowAbsent, the installer cannot force
404 + /// the child feature out of the absent state. In this case, the child feature matches the parent's
405 + /// installation state only if the child is set to something other than absent.</p>
406 + /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
407 + /// </p></remarks>
408 + FollowParent = 0x0002,
409 +
410 + /// <summary>
411 + /// Set this attribute and the feature state is Advertise.
412 + /// </summary>
413 + /// <remarks><p>
414 + /// If the feature is listed by the ADDDEFAULT property this bit is ignored and the feature state is determined
415 + /// according to FavorLocal and FavorSource.
416 + /// <p>Omit this attribute and the feature state is determined according to DisallowAdvertise and FavorLocal
417 + /// and FavorSource.</p>
418 + /// </p></remarks>
419 + FavorAdvertise = 0x0004,
420 +
421 + /// <summary>
422 + /// Set this attribute to prevent the feature from being advertised.
423 + /// </summary>
424 + /// <remarks><p>
425 + /// Note that this bit works only with features that are listed by the ADVERTISE property.
426 + /// <p>Set this attribute and if the listed feature is not a parent or child, the feature is installed according to
427 + /// FavorLocal and FavorSource.</p>
428 + /// <p>Set this attribute for the parent of a listed feature and the parent is installed.</p>
429 + /// <p>Set this attribute for the child of a listed feature and the state of the child is Absent.</p>
430 + /// <p>Omit this attribute and if the listed feature is not a parent or child, the feature state is Advertise.</p>
431 + /// <p>Omit this attribute and if the listed feature is a parent or child, the state of both features is Advertise.</p>
432 + /// </p></remarks>
433 + DisallowAdvertise = 0x0008,
434 +
435 + /// <summary>
436 + /// Set this attribute and the user interface does not display an option to change the feature state
437 + /// to Absent. Setting this attribute forces the feature to the installation state, whether or not the
438 + /// feature is visible in the UI.
439 + /// </summary>
440 + /// <remarks><p>
441 + /// Omit this attribute and the user interface displays an option to change the feature state to Absent.
442 + /// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
443 + /// <p>Setting this attribute not only affects the UI, but also forces the feature to the install state whether
444 + /// the feature is visible in the UI or not.</p>
445 + /// </p></remarks>
446 + UIDisallowAbsent = 0x0010,
447 +
448 + /// <summary>
449 + /// Set this attribute and advertising is disabled for the feature if the operating system shell does not
450 + /// support Windows Installer descriptors.
451 + /// </summary>
452 + NoUnsupportedAdvertise = 0x0020,
453 + }
454 +
455 + /// <summary>
456 + /// Available values for the Attributes column of the File table.
457 + /// </summary>
458 + [Flags]
459 + public enum FileAttributes : int
460 + {
461 + /// <summary>No attributes.</summary>
462 + None = 0x0000,
463 +
464 + /// <summary>Read-only.</summary>
465 + ReadOnly = 0x0001,
466 +
467 + /// <summary>Hidden.</summary>
468 + Hidden = 0x0002,
469 +
470 + /// <summary>System.</summary>
471 + System = 0x0004,
472 +
473 + /// <summary>The file is vital for the proper operation of the component to which it belongs.</summary>
474 + Vital = 0x0200,
475 +
476 + /// <summary>The file contains a valid checksum. A checksum is required to repair a file that has become corrupted.</summary>
477 + Checksum = 0x0400,
478 +
479 + /// <summary>This bit must only be added by a patch and if the file is being added by the patch.</summary>
480 + PatchAdded = 0x1000,
481 +
482 + /// <summary>
483 + /// The file's source type is uncompressed. If set, ignore the WordCount summary information property. If neither
484 + /// Noncompressed nor Compressed are set, the compression state of the file is specified by the WordCount summary
485 + /// information property. Do not set both Noncompressed and Compressed.
486 + /// </summary>
487 + NonCompressed = 0x2000,
488 +
489 + /// <summary>
490 + /// The file's source type is compressed. If set, ignore the WordCount summary information property. If neither
491 + /// Noncompressed or Compressed are set, the compression state of the file is specified by the WordCount summary
492 + /// information property. Do not set both Noncompressed and Compressed.
493 + /// </summary>
494 + Compressed = 0x4000,
495 + }
496 +
497 + /// <summary>
498 + /// Defines values for the Action column of the IniFile and RemoveIniFile tables.
499 + /// </summary>
500 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ini")]
501 + public enum IniFileAction : int
502 + {
503 + /// <summary>Creates or updates a .ini entry.</summary>
504 + AddLine = 0,
505 +
506 + /// <summary>Creates a .ini entry only if the entry does not already exist.</summary>
507 + CreateLine = 1,
508 +
509 + /// <summary>Deletes .ini entry.</summary>
510 + RemoveLine = 2,
511 +
512 + /// <summary>Creates a new entry or appends a new comma-separated value to an existing entry.</summary>
513 + AddTag = 3,
514 +
515 + /// <summary>Deletes a tag from a .ini entry.</summary>
516 + RemoveTag = 4,
517 + }
518 +
519 + /// <summary>
520 + /// Defines values for the Type column of the CompLocator, IniLocator, and RegLocator tables.
521 + /// </summary>
522 + [Flags]
523 + [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
524 + public enum LocatorTypes : int
525 + {
526 + /// <summary>Key path is a directory.</summary>
527 + Directory = 0x00000000,
528 +
529 + /// <summary>Key path is a file name.</summary>
530 + FileName = 0x00000001,
531 +
532 + /// <summary>Key path is a registry value.</summary>
533 + RawValue = 0x00000002,
534 +
535 + /// <summary>Set this bit to have the installer search the 64-bit portion of the registry.</summary>
536 + SixtyFourBit = 0x00000010,
537 + }
538 +
539 + /// <summary>
540 + /// Defines values for the Root column of the Registry, RemoveRegistry, and RegLocator tables.
541 + /// </summary>
542 + public enum RegistryRoot : int
543 + {
544 + /// <summary>HKEY_CURRENT_USER for a per-user installation,
545 + /// or HKEY_LOCAL_MACHINE for a per-machine installation.</summary>
546 + UserOrMachine = -1,
547 +
548 + /// <summary>HKEY_CLASSES_ROOT</summary>
549 + ClassesRoot = 0,
550 +
551 + /// <summary>HKEY_CURRENT_USER</summary>
552 + CurrentUser = 1,
553 +
554 + /// <summary>HKEY_LOCAL_MACHINE</summary>
555 + LocalMachine = 2,
556 +
557 + /// <summary>HKEY_USERS</summary>
558 + Users = 3,
559 + }
560 +
561 + /// <summary>
562 + /// Defines values for the InstallMode column of the RemoveFile table.
563 + /// </summary>
564 + [Flags]
565 + public enum RemoveFileModes : int
566 + {
567 + /// <summary>Never remove.</summary>
568 + None = 0,
569 +
570 + /// <summary>Remove when the associated component is being installed (install state = local or source).</summary>
571 + OnInstall = 1,
572 +
573 + /// <summary>Remove when the associated component is being removed (install state = absent).</summary>
574 + OnRemove = 2,
575 + }
576 +
577 + /// <summary>
578 + /// Defines values for the ServiceType, StartType, and ErrorControl columns of the ServiceInstall table.
579 + /// </summary>
580 + [Flags]
581 + public enum ServiceAttributes : int
582 + {
583 + /// <summary>No flags.</summary>
584 + None = 0,
585 +
586 + /// <summary>A Win32 service that runs its own process.</summary>
587 + OwnProcess = 0x0010,
588 +
589 + /// <summary>A Win32 service that shares a process.</summary>
590 + ShareProcess = 0x0020,
591 +
592 + /// <summary>A Win32 service that interacts with the desktop.
593 + /// This value cannot be used alone and must be added to either
594 + /// <see cref="OwnProcess"/> or <see cref="ShareProcess"/>.</summary>
595 + Interactive = 0x0100,
596 +
597 + /// <summary>Service starts during startup of the system.</summary>
598 + AutoStart = 0x0002,
599 +
600 + /// <summary>Service starts when the service control manager calls the StartService function.</summary>
601 + DemandStart = 0x0003,
602 +
603 + /// <summary>Specifies a service that can no longer be started.</summary>
604 + Disabled = 0x0004,
605 +
606 + /// <summary>Logs the error, displays a message box and continues the startup operation.</summary>
607 + ErrorMessage = 0x0001,
608 +
609 + /// <summary>Logs the error if it is possible and the system is restarted with the last configuration
610 + /// known to be good. If the last-known-good configuration is being started, the startup operation fails.</summary>
611 + ErrorCritical = 0x0003,
612 +
613 + /// <summary>When combined with other error flags, specifies that the overall install should fail if
614 + /// the service cannot be installed into the system.</summary>
615 + ErrorControlVital = 0x8000,
616 + }
617 +
618 + /// <summary>
619 + /// Defines values for the Event column of the ServiceControl table.
620 + /// </summary>
621 + [Flags]
622 + public enum ServiceControlEvents : int
623 + {
624 + /// <summary>No control events.</summary>
625 + None = 0x0000,
626 +
627 + /// <summary>During an install, starts the service during the StartServices action.</summary>
628 + Start = 0x0001,
629 +
630 + /// <summary>During an install, stops the service during the StopServices action.</summary>
631 + Stop = 0x0002,
632 +
633 + /// <summary>During an install, deletes the service during the DeleteServices action.</summary>
634 + Delete = 0x0008,
635 +
636 + /// <summary>During an uninstall, starts the service during the StartServices action.</summary>
637 + UninstallStart = 0x0010,
638 +
639 + /// <summary>During an uninstall, stops the service during the StopServices action.</summary>
640 + UninstallStop = 0x0020,
641 +
642 + /// <summary>During an uninstall, deletes the service during the DeleteServices action.</summary>
643 + UninstallDelete = 0x0080,
644 + }
645 +
646 + /// <summary>
647 + /// Defines values for the StyleBits column of the TextStyle table.
648 + /// </summary>
649 + [Flags]
650 + public enum TextStyles : int
651 + {
652 + /// <summary>Bold</summary>
653 + Bold = 0x0001,
654 +
655 + /// <summary>Italic</summary>
656 + Italic = 0x0002,
657 +
658 + /// <summary>Underline</summary>
659 + Underline = 0x0004,
660 +
661 + /// <summary>Strike out</summary>
662 + Strike = 0x0008,
663 + }
664 +
665 + /// <summary>
666 + /// Defines values for the Attributes column of the Upgrade table.
667 + /// </summary>
668 + [Flags]
669 + public enum UpgradeAttributes : int
670 + {
671 + /// <summary>Migrates feature states by enabling the logic in the MigrateFeatureStates action.</summary>
672 + MigrateFeatures = 0x0001,
673 +
674 + /// <summary>Detects products and applications but does not remove.</summary>
675 + OnlyDetect = 0x0002,
676 +
677 + /// <summary>Continues installation upon failure to remove a product or application.</summary>
678 + IgnoreRemoveFailure = 0x0004,
679 +
680 + /// <summary>Detects the range of versions including the value in VersionMin.</summary>
681 + VersionMinInclusive = 0x0100,
682 +
683 + /// <summary>Dectects the range of versions including the value in VersionMax.</summary>
684 + VersionMaxInclusive = 0x0200,
685 +
686 + /// <summary>Detects all languages, excluding the languages listed in the Language column.</summary>
687 + LanguagesExclusive = 0x0400,
688 + }
689 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ColumnInfo.cs new
+297
@@ -0,0 +1,297 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// Defines a single column of a table in an installer database.
13 + /// </summary>
14 + /// <remarks>Once created, a ColumnInfo object is immutable.</remarks>
15 + public class ColumnInfo
16 + {
17 + private string name;
18 + private Type type;
19 + private int size;
20 + private bool isRequired;
21 + private bool isTemporary;
22 + private bool isLocalizable;
23 +
24 + /// <summary>
25 + /// Creates a new ColumnInfo object from a column definition.
26 + /// </summary>
27 + /// <param name="name">name of the column</param>
28 + /// <param name="columnDefinition">column definition string</param>
29 + /// <seealso cref="ColumnDefinitionString"/>
30 + public ColumnInfo(string name, string columnDefinition)
31 + : this(name, typeof(String), 0, false, false, false)
32 + {
33 + if (name == null)
34 + {
35 + throw new ArgumentNullException("name");
36 + }
37 +
38 + if (columnDefinition == null)
39 + {
40 + throw new ArgumentNullException("columnDefinition");
41 + }
42 +
43 + switch (Char.ToLower(columnDefinition[0], CultureInfo.InvariantCulture))
44 + {
45 + case 'i': this.type = typeof(Int32);
46 + break;
47 + case 'j': this.type = typeof(Int32); this.isTemporary = true;
48 + break;
49 + case 'g': this.type = typeof(String); this.isTemporary = true;
50 + break;
51 + case 'l': this.type = typeof(String); this.isLocalizable = true;
52 + break;
53 + case 'o': this.type = typeof(Stream); this.isTemporary = true;
54 + break;
55 + case 's': this.type = typeof(String);
56 + break;
57 + case 'v': this.type = typeof(Stream);
58 + break;
59 + default: throw new InstallerException();
60 + }
61 +
62 + this.isRequired = Char.IsLower(columnDefinition[0]);
63 + this.size = Int32.Parse(
64 + columnDefinition.Substring(1),
65 + CultureInfo.InvariantCulture.NumberFormat);
66 + if (this.type == typeof(Int32) && this.size <= 2)
67 + {
68 + this.type = typeof(Int16);
69 + }
70 + }
71 +
72 + /// <summary>
73 + /// Creates a new ColumnInfo object from a list of parameters.
74 + /// </summary>
75 + /// <param name="name">name of the column</param>
76 + /// <param name="type">type of the column; must be one of the following:
77 + /// Int16, Int32, String, or Stream</param>
78 + /// <param name="size">the maximum number of characters for String columns;
79 + /// ignored for other column types</param>
80 + /// <param name="isRequired">true if the column is required to have a non-null value</param>
81 + public ColumnInfo(string name, Type type, int size, bool isRequired)
82 + : this(name, type, size, isRequired, false, false)
83 + {
84 + }
85 +
86 + /// <summary>
87 + /// Creates a new ColumnInfo object from a list of parameters.
88 + /// </summary>
89 + /// <param name="name">name of the column</param>
90 + /// <param name="type">type of the column; must be one of the following:
91 + /// Int16, Int32, String, or Stream</param>
92 + /// <param name="size">the maximum number of characters for String columns;
93 + /// ignored for other column types</param>
94 + /// <param name="isRequired">true if the column is required to have a non-null value</param>
95 + /// <param name="isTemporary">true to if the column is only in-memory and
96 + /// not persisted with the database</param>
97 + /// <param name="isLocalizable">for String columns, indicates the column
98 + /// is localizable; ignored for other column types</param>
99 + public ColumnInfo(string name, Type type, int size, bool isRequired, bool isTemporary, bool isLocalizable)
100 + {
101 + if (name == null)
102 + {
103 + throw new ArgumentNullException("name");
104 + }
105 +
106 + if (type == typeof(Int32))
107 + {
108 + size = 4;
109 + isLocalizable = false;
110 + }
111 + else if (type == typeof(Int16))
112 + {
113 + size = 2;
114 + isLocalizable = false;
115 + }
116 + else if (type == typeof(String))
117 + {
118 + }
119 + else if (type == typeof(Stream))
120 + {
121 + isLocalizable = false;
122 + }
123 + else
124 + {
125 + throw new ArgumentOutOfRangeException("type");
126 + }
127 +
128 + this.name = name;
129 + this.type = type;
130 + this.size = size;
131 + this.isRequired = isRequired;
132 + this.isTemporary = isTemporary;
133 + this.isLocalizable = isLocalizable;
134 + }
135 +
136 + /// <summary>
137 + /// Gets the name of the column.
138 + /// </summary>
139 + /// <value>name of the column</value>
140 + public string Name
141 + {
142 + get { return this.name; }
143 + }
144 +
145 + /// <summary>
146 + /// Gets the type of the column as a System.Type. This is one of the following: Int16, Int32, String, or Stream
147 + /// </summary>
148 + /// <value>type of the column</value>
149 + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
150 + public Type Type
151 + {
152 + get { return this.type; }
153 + }
154 +
155 + /// <summary>
156 + /// Gets the type of the column as an integer that can be cast to a System.Data.DbType. This is one of the following: Int16, Int32, String, or Binary
157 + /// </summary>
158 + /// <value>equivalent DbType of the column as an integer</value>
159 + public int DBType
160 + {
161 + get
162 + {
163 + if (this.type == typeof(Int16)) return 10;
164 + else if (this.type == typeof(Int32)) return 11;
165 + else if (this.type == typeof(Stream)) return 1;
166 + else return 16;
167 + }
168 + }
169 +
170 + /// <summary>
171 + /// Gets the size of the column.
172 + /// </summary>
173 + /// <value>The size of integer columns this is either 2 or 4. For string columns this is the maximum
174 + /// recommended length of the string, or 0 for unlimited length. For stream columns, 0 is returned.</value>
175 + public int Size
176 + {
177 + get { return this.size; }
178 + }
179 +
180 + /// <summary>
181 + /// Gets a value indicating whether the column must be non-null when inserting a record.
182 + /// </summary>
183 + /// <value>required status of the column</value>
184 + public bool IsRequired
185 + {
186 + get { return this.isRequired; }
187 + }
188 +
189 + /// <summary>
190 + /// Gets a value indicating whether the column is temporary. Temporary columns are not persisted
191 + /// when the database is saved to disk.
192 + /// </summary>
193 + /// <value>temporary status of the column</value>
194 + public bool IsTemporary
195 + {
196 + get { return this.isTemporary; }
197 + }
198 +
199 + /// <summary>
200 + /// Gets a value indicating whether the column is a string column that is localizable.
201 + /// </summary>
202 + /// <value>localizable status of the column</value>
203 + public bool IsLocalizable
204 + {
205 + get { return this.isLocalizable; }
206 + }
207 +
208 + /// <summary>
209 + /// Gets an SQL fragment that can be used to create this column within a CREATE TABLE statement.
210 + /// </summary>
211 + /// <value>SQL fragment to be used for creating the column</value>
212 + /// <remarks><p>
213 + /// Examples:
214 + /// <list type="bullet">
215 + /// <item>LONG</item>
216 + /// <item>SHORT TEMPORARY</item>
217 + /// <item>CHAR(0) LOCALIZABLE</item>
218 + /// <item>CHAR(72) NOT NULL LOCALIZABLE</item>
219 + /// <item>OBJECT</item>
220 + /// </list>
221 + /// </p></remarks>
222 + public string SqlCreateString
223 + {
224 + get
225 + {
226 + StringBuilder s = new StringBuilder();
227 + s.AppendFormat("`{0}` ", this.name);
228 + if (this.type == typeof(Int16)) s.Append("SHORT");
229 + else if (this.type == typeof(Int32)) s.Append("LONG");
230 + else if (this.type == typeof(String)) s.AppendFormat("CHAR({0})", this.size);
231 + else s.Append("OBJECT");
232 + if (this.isRequired) s.Append(" NOT NULL");
233 + if (this.isTemporary) s.Append(" TEMPORARY");
234 + if (this.isLocalizable) s.Append(" LOCALIZABLE");
235 + return s.ToString();
236 + }
237 + }
238 +
239 + /// <summary>
240 + /// Gets a short string defining the type and size of the column.
241 + /// </summary>
242 + /// <value>
243 + /// The definition string consists
244 + /// of a single letter representing the data type followed by the width of the column (in characters
245 + /// when applicable, bytes otherwise). A width of zero designates an unbounded width (for example,
246 + /// long text fields and streams). An uppercase letter indicates that null values are allowed in
247 + /// the column.
248 + /// </value>
249 + /// <remarks><p>
250 + /// <list>
251 + /// <item>s? - String, variable length (?=1-255)</item>
252 + /// <item>s0 - String, variable length</item>
253 + /// <item>i2 - Short integer</item>
254 + /// <item>i4 - Long integer</item>
255 + /// <item>v0 - Binary Stream</item>
256 + /// <item>g? - Temporary string (?=0-255)</item>
257 + /// <item>j? - Temporary integer (?=0,1,2,4)</item>
258 + /// <item>O0 - Temporary object (stream)</item>
259 + /// <item>l? - Localizable string, variable length (?=1-255)</item>
260 + /// <item>l0 - Localizable string, variable length</item>
261 + /// </list>
262 + /// </p></remarks>
263 + public string ColumnDefinitionString
264 + {
265 + get
266 + {
267 + char t;
268 + if (this.type == typeof(Int16) || this.type == typeof(Int32))
269 + {
270 + t = (this.isTemporary ? 'j' : 'i');
271 + }
272 + else if (this.type == typeof(String))
273 + {
274 + t = (this.isTemporary ? 'g' : this.isLocalizable ? 'l' : 's');
275 + }
276 + else
277 + {
278 + t = (this.isTemporary ? 'O' : 'v');
279 + }
280 + return String.Format(
281 + CultureInfo.InvariantCulture,
282 + "{0}{1}",
283 + (this.isRequired ? t : Char.ToUpper(t, CultureInfo.InvariantCulture)),
284 + this.size);
285 + }
286 + }
287 +
288 + /// <summary>
289 + /// Gets the name of the column.
290 + /// </summary>
291 + /// <returns>Name of the column.</returns>
292 + public override string ToString()
293 + {
294 + return this.Name;
295 + }
296 + }
297 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInfo.cs new
+276
@@ -0,0 +1,276 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 +
10 + /// <summary>
11 + /// Accessor for information about components within the context of an installation session.
12 + /// </summary>
13 + public sealed class ComponentInfoCollection : ICollection<ComponentInfo>
14 + {
15 + private Session session;
16 +
17 + internal ComponentInfoCollection(Session session)
18 + {
19 + this.session = session;
20 + }
21 +
22 + /// <summary>
23 + /// Gets information about a component within the context of an installation session.
24 + /// </summary>
25 + /// <param name="component">name of the component</param>
26 + /// <returns>component object</returns>
27 + public ComponentInfo this[string component]
28 + {
29 + get
30 + {
31 + return new ComponentInfo(this.session, component);
32 + }
33 + }
34 +
35 + void ICollection<ComponentInfo>.Add(ComponentInfo item)
36 + {
37 + throw new InvalidOperationException();
38 + }
39 +
40 + void ICollection<ComponentInfo>.Clear()
41 + {
42 + throw new InvalidOperationException();
43 + }
44 +
45 + /// <summary>
46 + /// Checks if the collection contains a component.
47 + /// </summary>
48 + /// <param name="component">name of the component</param>
49 + /// <returns>true if the component is in the collection, else false</returns>
50 + public bool Contains(string component)
51 + {
52 + return this.session.Database.CountRows(
53 + "Component", "`Component` = '" + component + "'") == 1;
54 + }
55 +
56 + bool ICollection<ComponentInfo>.Contains(ComponentInfo item)
57 + {
58 + return item != null && this.Contains(item.Name);
59 + }
60 +
61 + /// <summary>
62 + /// Copies the features into an array.
63 + /// </summary>
64 + /// <param name="array">array that receives the features</param>
65 + /// <param name="arrayIndex">offset into the array</param>
66 + public void CopyTo(ComponentInfo[] array, int arrayIndex)
67 + {
68 + foreach (ComponentInfo component in this)
69 + {
70 + array[arrayIndex++] = component;
71 + }
72 + }
73 +
74 + /// <summary>
75 + /// Gets the number of components defined for the product.
76 + /// </summary>
77 + public int Count
78 + {
79 + get
80 + {
81 + return this.session.Database.CountRows("Component");
82 + }
83 + }
84 +
85 + bool ICollection<ComponentInfo>.IsReadOnly
86 + {
87 + get
88 + {
89 + return true;
90 + }
91 + }
92 +
93 + bool ICollection<ComponentInfo>.Remove(ComponentInfo item)
94 + {
95 + throw new InvalidOperationException();
96 + }
97 +
98 + /// <summary>
99 + /// Enumerates the components in the collection.
100 + /// </summary>
101 + /// <returns>an enumerator over all features in the collection</returns>
102 + public IEnumerator<ComponentInfo> GetEnumerator()
103 + {
104 + using (View compView = this.session.Database.OpenView(
105 + "SELECT `Component` FROM `Component`"))
106 + {
107 + compView.Execute();
108 +
109 + foreach (Record compRec in compView) using (compRec)
110 + {
111 + string comp = compRec.GetString(1);
112 + yield return new ComponentInfo(this.session, comp);
113 + }
114 + }
115 + }
116 +
117 + IEnumerator IEnumerable.GetEnumerator()
118 + {
119 + return this.GetEnumerator();
120 + }
121 + }
122 +
123 + /// <summary>
124 + /// Provides access to information about a component within the context of an installation session.
125 + /// </summary>
126 + public class ComponentInfo
127 + {
128 + private Session session;
129 + private string name;
130 +
131 + internal ComponentInfo(Session session, string name)
132 + {
133 + this.session = session;
134 + this.name = name;
135 + }
136 +
137 + /// <summary>
138 + /// Gets the name of the component (primary key in the Component table).
139 + /// </summary>
140 + public string Name
141 + {
142 + get
143 + {
144 + return this.name;
145 + }
146 + }
147 +
148 + /// <summary>
149 + /// Gets the current install state of the designated Component.
150 + /// </summary>
151 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
152 + /// <exception cref="ArgumentException">an unknown Component was requested</exception>
153 + /// <remarks><p>
154 + /// Win32 MSI API:
155 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>
156 + /// </p></remarks>
157 + public InstallState CurrentState
158 + {
159 + get
160 + {
161 + int installedState, actionState;
162 + uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
163 + if (ret != 0)
164 + {
165 + if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
166 + {
167 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
168 + }
169 + else
170 + {
171 + throw InstallerException.ExceptionFromReturnCode(ret);
172 + }
173 + }
174 + return (InstallState) installedState;
175 + }
176 + }
177 +
178 + /// <summary>
179 + /// Gets or sets the action state of the designated Component.
180 + /// </summary>
181 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
182 + /// <exception cref="ArgumentException">an unknown Component was requested</exception>
183 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
184 + /// <remarks><p>
185 + /// Win32 MSI APIs:
186 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>,
187 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetcomponentstate.asp">MsiSetComponentState</a>
188 + /// </p></remarks>
189 + public InstallState RequestState
190 + {
191 + get
192 + {
193 + int installedState, actionState;
194 + uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
195 + if (ret != 0)
196 + {
197 + if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
198 + {
199 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
200 + }
201 + else
202 + {
203 + throw InstallerException.ExceptionFromReturnCode(ret);
204 + }
205 + }
206 + return (InstallState) actionState;
207 + }
208 +
209 + set
210 + {
211 + uint ret = RemotableNativeMethods.MsiSetComponentState((int) this.session.Handle, this.name, (int) value);
212 + if (ret != 0)
213 + {
214 + if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
215 + {
216 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
217 + }
218 + else
219 + {
220 + throw InstallerException.ExceptionFromReturnCode(ret);
221 + }
222 + }
223 + }
224 + }
225 +
226 + /// <summary>
227 + /// Gets disk space per drive required to install a component.
228 + /// </summary>
229 + /// <param name="installState">Requested component state</param>
230 + /// <returns>A list of InstallCost structures, specifying the cost for each drive for the component</returns>
231 + /// <remarks><p>
232 + /// Win32 MSI API:
233 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a>
234 + /// </p></remarks>
235 + public IList<InstallCost> GetCost(InstallState installState)
236 + {
237 + IList<InstallCost> costs = new List<InstallCost>();
238 + StringBuilder driveBuf = new StringBuilder(20);
239 + for (uint i = 0; true; i++)
240 + {
241 + int cost, tempCost;
242 + uint driveBufSize = (uint) driveBuf.Capacity;
243 + uint ret = RemotableNativeMethods.MsiEnumComponentCosts(
244 + (int) this.session.Handle,
245 + this.name,
246 + i,
247 + (int) installState,
248 + driveBuf,
249 + ref driveBufSize,
250 + out cost,
251 + out tempCost);
252 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
253 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
254 + {
255 + driveBuf.Capacity = (int) ++driveBufSize;
256 + ret = RemotableNativeMethods.MsiEnumComponentCosts(
257 + (int) this.session.Handle,
258 + this.name,
259 + i,
260 + (int) installState,
261 + driveBuf,
262 + ref driveBufSize,
263 + out cost,
264 + out tempCost);
265 + }
266 +
267 + if (ret != 0)
268 + {
269 + throw InstallerException.ExceptionFromReturnCode(ret);
270 + }
271 + costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L));
272 + }
273 + return costs;
274 + }
275 + }
276 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ComponentInstallation.cs new
+382
@@ -0,0 +1,382 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + /// <summary>
11 + /// Represents an instance of a registered component.
12 + /// </summary>
13 + public class ComponentInstallation : InstallationPart
14 + {
15 + /// <summary>
16 + /// Gets the set of installed components for all products.
17 + /// </summary>
18 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
19 + /// <remarks><p>
20 + /// Win32 MSI API:
21 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponents.asp">MsiEnumComponents</a>
22 + /// </p></remarks>
23 + public static IEnumerable<ComponentInstallation> AllComponents
24 + {
25 + get
26 + {
27 + StringBuilder buf = new StringBuilder(40);
28 + for (uint i = 0; true; i++)
29 + {
30 + uint ret = NativeMethods.MsiEnumComponents(i, buf);
31 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
32 + if (ret != 0)
33 + {
34 + throw InstallerException.ExceptionFromReturnCode(ret);
35 + }
36 + yield return new ComponentInstallation(buf.ToString());
37 + }
38 + }
39 + }
40 +
41 + /// <summary>
42 + /// Gets the set of installed components for products in the indicated context.
43 + /// </summary>
44 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
45 + /// <remarks><p>
46 + /// Win32 MSI API:
47 + /// <a href="http://msdn.microsoft.com/library/dd407947.aspx">MsiEnumComponentsEx</a>
48 + /// </p></remarks>
49 + public static IEnumerable<ComponentInstallation> Components(string szUserSid, UserContexts dwContext)
50 + {
51 + uint pcchSid = 32;
52 + StringBuilder szSid = new StringBuilder((int)pcchSid);
53 + StringBuilder buf = new StringBuilder(40);
54 + UserContexts installedContext;
55 + for (uint i = 0; true; i++)
56 + {
57 + uint ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
58 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
59 + {
60 + szSid.EnsureCapacity((int) ++pcchSid);
61 + ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
62 + }
63 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
64 + if (ret != 0)
65 + {
66 + throw InstallerException.ExceptionFromReturnCode(ret);
67 + }
68 + yield return new ComponentInstallation(buf.ToString(), szSid.ToString(), installedContext);
69 + }
70 + }
71 + private static string GetProductCode(string component)
72 + {
73 + StringBuilder buf = new StringBuilder(40);
74 + uint ret = NativeMethods.MsiGetProductCode(component, buf);
75 + if (ret != 0)
76 + {
77 + return null;
78 + }
79 +
80 + return buf.ToString();
81 + }
82 +
83 + private static string GetProductCode(string component, string szUserSid, UserContexts dwContext)
84 + {
85 + // TODO: We really need what would be MsiGetProductCodeEx, or at least a reasonable facimile thereof (something that restricts the search to the context defined by szUserSid & dwContext)
86 + return GetProductCode(component);
87 + }
88 +
89 + /// <summary>
90 + /// Creates a new ComponentInstallation, automatically detecting the
91 + /// product that the component is a part of.
92 + /// </summary>
93 + /// <param name="componentCode">component GUID</param>
94 + /// <remarks><p>
95 + /// Win32 MSI API:
96 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductcode.asp">MsiGetProductCode</a>
97 + /// </p></remarks>
98 + public ComponentInstallation(string componentCode)
99 + : this(componentCode, ComponentInstallation.GetProductCode(componentCode))
100 + {
101 + }
102 +
103 + /// <summary>
104 + /// Creates a new ComponentInstallation, automatically detecting the
105 + /// product that the component is a part of.
106 + /// </summary>
107 + /// <param name="componentCode">component GUID</param>
108 + /// <param name="szUserSid">context user SID</param>
109 + /// <param name="dwContext">user contexts</param>
110 + public ComponentInstallation(string componentCode, string szUserSid, UserContexts dwContext)
111 + : this(componentCode, ComponentInstallation.GetProductCode(componentCode, szUserSid, dwContext), szUserSid, dwContext)
112 + {
113 + }
114 +
115 + /// <summary>
116 + /// Creates a new ComponentInstallation for a component installed by a
117 + /// specific product.
118 + /// </summary>
119 + /// <param name="componentCode">component GUID</param>
120 + /// <param name="productCode">ProductCode GUID</param>
121 + public ComponentInstallation(string componentCode, string productCode)
122 + : this(componentCode, productCode, null, UserContexts.None)
123 + {
124 + }
125 +
126 + /// <summary>
127 + /// Creates a new ComponentInstallation for a component installed by a
128 + /// specific product.
129 + /// </summary>
130 + /// <param name="componentCode">component GUID</param>
131 + /// <param name="productCode">ProductCode GUID</param>
132 + /// <param name="szUserSid">context user SID</param>
133 + /// <param name="dwContext">user contexts</param>
134 + public ComponentInstallation(string componentCode, string productCode, string szUserSid, UserContexts dwContext)
135 + : base(componentCode, productCode, szUserSid, dwContext)
136 + {
137 + if (string.IsNullOrEmpty(componentCode))
138 + {
139 + throw new ArgumentNullException("componentCode");
140 + }
141 + }
142 +
143 + /// <summary>
144 + /// Gets the component code (GUID) of the component.
145 + /// </summary>
146 + public string ComponentCode
147 + {
148 + get
149 + {
150 + return this.Id;
151 + }
152 + }
153 +
154 + /// <summary>
155 + /// Gets all client products of a specified component.
156 + /// </summary>
157 + /// <returns>enumeration over all client products of the component</returns>
158 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
159 + /// <remarks><p>
160 + /// Because clients are not ordered, any new component has an arbitrary index.
161 + /// This means that the property may return clients in any order.
162 + /// </p><p>
163 + /// Win32 MSI API:
164 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumclients.asp">MsiEnumClients</a>,
165 + /// <a href="http://msdn.microsoft.com/library/dd407946.aspx">MsiEnumClientsEx</a>
166 + /// </p></remarks>
167 + public IEnumerable<ProductInstallation> ClientProducts
168 + {
169 + get
170 + {
171 + StringBuilder buf = new StringBuilder(40);
172 + for (uint i = 0; true; i++)
173 + {
174 + uint chSid = 0;
175 + UserContexts installedContext;
176 + uint ret = this.Context == UserContexts.None ?
177 + NativeMethods.MsiEnumClients(this.ComponentCode, i, buf) :
178 + NativeMethods.MsiEnumClientsEx(this.ComponentCode, this.UserSid, this.Context, i, buf, out installedContext, null, ref chSid);
179 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
180 + else if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) break;
181 + if (ret != 0)
182 + {
183 + throw InstallerException.ExceptionFromReturnCode(ret);
184 + }
185 + yield return new ProductInstallation(buf.ToString());
186 + }
187 + }
188 + }
189 +
190 + /// <summary>
191 + /// Gets the installed state of a component.
192 + /// </summary>
193 + /// <returns>the installed state of the component, or InstallState.Unknown
194 + /// if this component is not part of a product</returns>
195 + /// <remarks><p>
196 + /// Win32 MSI API:
197 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
198 + /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>
199 + /// </p></remarks>
200 + public override InstallState State
201 + {
202 + get
203 + {
204 + if (this.ProductCode != null)
205 + {
206 + uint bufSize = 0;
207 + int installState = this.Context == UserContexts.None ?
208 + NativeMethods.MsiGetComponentPath(
209 + this.ProductCode, this.ComponentCode, null, ref bufSize) :
210 + NativeMethods.MsiGetComponentPathEx(
211 + this.ProductCode, this.ComponentCode, this.UserSid, this.Context, null, ref bufSize);
212 + return (InstallState) installState;
213 + }
214 + else
215 + {
216 + return InstallState.Unknown;
217 + }
218 + }
219 + }
220 +
221 + /// <summary>
222 + /// Gets the full path to an installed component. If the key path for the component is a
223 + /// registry key then the registry key is returned.
224 + /// </summary>
225 + /// <returns>The file or registry keypath to the component, or null if the component is not available.</returns>
226 + /// <exception cref="ArgumentException">An unknown product or component was specified</exception>
227 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
228 + /// <remarks><p>
229 + /// If the component is a registry key, the registry roots are represented numerically.
230 + /// For example, a registry path of "HKEY_CURRENT_USER\SOFTWARE\Microsoft" would be returned
231 + /// as "01:\SOFTWARE\Microsoft". The registry roots returned are defined as follows:
232 + /// HKEY_CLASSES_ROOT=00, HKEY_CURRENT_USER=01, HKEY_LOCAL_MACHINE=02, HKEY_USERS=03,
233 + /// HKEY_PERFORMANCE_DATA=04
234 + /// </p><p>
235 + /// Win32 MSI APIs:
236 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
237 + /// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>,
238 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msilocatecomponent.asp">MsiLocateComponent</a>
239 + /// </p></remarks>
240 + public string Path
241 + {
242 + get
243 + {
244 + StringBuilder buf = new StringBuilder(256);
245 + uint bufSize = (uint) buf.Capacity;
246 + InstallState installState;
247 +
248 + if (this.ProductCode != null)
249 + {
250 + installState = (this.Context == UserContexts.None) ?
251 + (InstallState) NativeMethods.MsiGetComponentPath(
252 + this.ProductCode, this.ComponentCode, buf, ref bufSize) :
253 + (InstallState) NativeMethods.MsiGetComponentPathEx(
254 + this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
255 + if (installState == InstallState.MoreData)
256 + {
257 + buf.Capacity = (int) ++bufSize;
258 + installState = (this.Context == UserContexts.None) ?
259 + (InstallState) NativeMethods.MsiGetComponentPath(
260 + this.ProductCode, this.ComponentCode, buf, ref bufSize) :
261 + (InstallState) NativeMethods.MsiGetComponentPathEx(
262 + this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
263 + }
264 + }
265 + else
266 + {
267 + installState = (InstallState) NativeMethods.MsiLocateComponent(
268 + this.ComponentCode, buf, ref bufSize);
269 + if (installState == InstallState.MoreData)
270 + {
271 + buf.Capacity = (int) ++bufSize;
272 + installState = (InstallState) NativeMethods.MsiLocateComponent(
273 + this.ComponentCode, buf, ref bufSize);
274 + }
275 + }
276 +
277 + switch (installState)
278 + {
279 + case InstallState.Local:
280 + case InstallState.Source:
281 + case InstallState.SourceAbsent:
282 + return buf.ToString();
283 +
284 + default:
285 + return null;
286 + }
287 + }
288 + }
289 +
290 + /// <summary>
291 + /// Gets the set of registered qualifiers for the component.
292 + /// </summary>
293 + /// <returns>Enumeration of the qulifiers for the component.</returns>
294 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
295 + /// <remarks><p>
296 + /// Because qualifiers are not ordered, any new qualifier has an arbitrary index,
297 + /// meaning the function can return qualifiers in any order.
298 + /// </p><p>
299 + /// Win32 MSI API:
300 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
301 + /// </p></remarks>
302 + public IEnumerable<ComponentInstallation.Qualifier> Qualifiers
303 + {
304 + get
305 + {
306 + StringBuilder qualBuf = new StringBuilder(255);
307 + StringBuilder dataBuf = new StringBuilder(255);
308 + for (uint i = 0; ; i++)
309 + {
310 + uint qualBufSize = (uint) qualBuf.Capacity;
311 + uint dataBufSize = (uint) dataBuf.Capacity;
312 + uint ret = NativeMethods.MsiEnumComponentQualifiers(
313 + this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
314 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
315 + {
316 + qualBuf.Capacity = (int) ++qualBufSize;
317 + dataBuf.Capacity = (int) ++dataBufSize;
318 + ret = NativeMethods.MsiEnumComponentQualifiers(
319 + this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
320 + }
321 +
322 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS ||
323 + ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
324 + {
325 + break;
326 + }
327 +
328 + if (ret != 0)
329 + {
330 + throw InstallerException.ExceptionFromReturnCode(ret);
331 + }
332 +
333 + yield return new ComponentInstallation.Qualifier(
334 + qualBuf.ToString(), dataBuf.ToString());
335 + }
336 + }
337 + }
338 +
339 + /// <summary>
340 + /// Holds data about a component qualifier.
341 + /// </summary>
342 + /// <remarks><p>
343 + /// Win32 MSI API:
344 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
345 + /// </p></remarks>
346 + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
347 + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
348 + public struct Qualifier
349 + {
350 + private string qualifierCode;
351 + private string data;
352 +
353 + internal Qualifier(string qualifierCode, string data)
354 + {
355 + this.qualifierCode = qualifierCode;
356 + this.data = data;
357 + }
358 +
359 + /// <summary>
360 + /// Gets the qualifier code.
361 + /// </summary>
362 + public string QualifierCode
363 + {
364 + get
365 + {
366 + return this.qualifierCode;
367 + }
368 + }
369 +
370 + /// <summary>
371 + /// Gets the qualifier data.
372 + /// </summary>
373 + public string Data
374 + {
375 + get
376 + {
377 + return this.data;
378 + }
379 + }
380 + }
381 + }
382 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionAttribute.cs new
+55
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Reflection;
7 +
8 + /// <summary>
9 + /// Marks a method as a custom action entry point.
10 + /// </summary>
11 + /// <remarks><p>
12 + /// A custom action method must be defined as public and static,
13 + /// take a single <see cref="Session"/> object as a parameter,
14 + /// and return an <see cref="ActionResult"/> enumeration value.
15 + /// </p></remarks>
16 + [Serializable, AttributeUsage(AttributeTargets.Method)]
17 + public sealed class CustomActionAttribute : Attribute
18 + {
19 + /// <summary>
20 + /// Name of the custom action entrypoint, or null if the same as the method name.
21 + /// </summary>
22 + private string name;
23 +
24 + /// <summary>
25 + /// Marks a method as a custom action entry point.
26 + /// </summary>
27 + public CustomActionAttribute()
28 + : this(null)
29 + {
30 + }
31 +
32 + /// <summary>
33 + /// Marks a method as a custom action entry point.
34 + /// </summary>
35 + /// <param name="name">Name of the function to be exported,
36 + /// defaults to the name of this method</param>
37 + public CustomActionAttribute(string name)
38 + {
39 + this.name = name;
40 + }
41 +
42 + /// <summary>
43 + /// Gets or sets the name of the custom action entrypoint. A null
44 + /// value defaults to the name of the method.
45 + /// </summary>
46 + /// <value>name of the custom action entrypoint, or null if none was specified</value>
47 + public string Name
48 + {
49 + get
50 + {
51 + return this.name;
52 + }
53 + }
54 + }
55 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/CustomActionProxy.cs new
+321
@@ -0,0 +1,321 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Security;
9 + using System.Reflection;
10 + using System.Collections;
11 + using System.Configuration;
12 + using System.Runtime.InteropServices;
13 + using System.Diagnostics.CodeAnalysis;
14 +
15 + /// <summary>
16 + /// Managed-code portion of the custom action proxy.
17 + /// </summary>
18 + internal static class CustomActionProxy
19 + {
20 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
21 + public static int InvokeCustomAction32(int sessionHandle, string entryPoint,
22 + int remotingDelegatePtr)
23 + {
24 + return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
25 + }
26 +
27 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
28 + public static int InvokeCustomAction64(int sessionHandle, string entryPoint,
29 + long remotingDelegatePtr)
30 + {
31 + return CustomActionProxy.InvokeCustomAction(sessionHandle, entryPoint, new IntPtr(remotingDelegatePtr));
32 + }
33 +
34 + /// <summary>
35 + /// Invokes a managed custom action method.
36 + /// </summary>
37 + /// <param name="sessionHandle">Integer handle to the installer session.</param>
38 + /// <param name="entryPoint">Name of the custom action entrypoint. This must
39 + /// either map to an entrypoint definition in the <c>customActions</c>
40 + /// config section, or be an explicit entrypoint of the form:
41 + /// &quot;AssemblyName!Namespace.Class.Method&quot;</param>
42 + /// <param name="remotingDelegatePtr">Pointer to a delegate used to
43 + /// make remote API calls, if this custom action is running out-of-proc.</param>
44 + /// <returns>The value returned by the custom action method,
45 + /// or ERROR_INSTALL_FAILURE if the custom action could not be invoked.</returns>
46 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
47 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
48 + public static int InvokeCustomAction(int sessionHandle, string entryPoint,
49 + IntPtr remotingDelegatePtr)
50 + {
51 + Session session = null;
52 + string assemblyName, className, methodName;
53 + MethodInfo method;
54 +
55 + try
56 + {
57 + MsiRemoteInvoke remotingDelegate = (MsiRemoteInvoke)
58 + Marshal.GetDelegateForFunctionPointer(
59 + remotingDelegatePtr, typeof(MsiRemoteInvoke));
60 + RemotableNativeMethods.RemotingDelegate = remotingDelegate;
61 +
62 + sessionHandle = RemotableNativeMethods.MakeRemoteHandle(sessionHandle);
63 + session = new Session((IntPtr) sessionHandle, false);
64 + if (String.IsNullOrEmpty(entryPoint))
65 + {
66 + throw new ArgumentNullException("entryPoint");
67 + }
68 +
69 + if (!CustomActionProxy.FindEntryPoint(
70 + session,
71 + entryPoint,
72 + out assemblyName,
73 + out className,
74 + out methodName))
75 + {
76 + return (int) ActionResult.Failure;
77 + }
78 + session.Log("Calling custom action {0}!{1}.{2}", assemblyName, className, methodName);
79 +
80 + method = CustomActionProxy.GetCustomActionMethod(
81 + session,
82 + assemblyName,
83 + className,
84 + methodName);
85 + if (method == null)
86 + {
87 + return (int) ActionResult.Failure;
88 + }
89 + }
90 + catch (Exception ex)
91 + {
92 + if (session != null)
93 + {
94 + try
95 + {
96 + session.Log("Exception while loading custom action:");
97 + session.Log(ex.ToString());
98 + }
99 + catch (Exception) { }
100 + }
101 + return (int) ActionResult.Failure;
102 + }
103 +
104 + try
105 + {
106 + // Set the current directory to the location of the extracted files.
107 + Environment.CurrentDirectory =
108 + AppDomain.CurrentDomain.BaseDirectory;
109 +
110 + object[] args = new object[] { session };
111 + if (DebugBreakEnabled(new string[] { entryPoint, methodName }))
112 + {
113 + string message = String.Format(
114 + "To debug your custom action, attach to process ID {0} (0x{0:x}) and click OK; otherwise, click Cancel to fail the custom action.",
115 + System.Diagnostics.Process.GetCurrentProcess().Id
116 + );
117 +
118 + MessageResult button = NativeMethods.MessageBox(
119 + IntPtr.Zero,
120 + message,
121 + "Custom Action Breakpoint",
122 + (int)MessageButtons.OKCancel | (int)MessageIcon.Asterisk | (int)(MessageBoxStyles.TopMost | MessageBoxStyles.ServiceNotification)
123 + );
124 +
125 + if (MessageResult.Cancel == button)
126 + {
127 + return (int)ActionResult.UserExit;
128 + }
129 + }
130 +
131 + ActionResult result = (ActionResult) method.Invoke(null, args);
132 + session.Close();
133 + return (int) result;
134 + }
135 + catch (InstallCanceledException)
136 + {
137 + return (int) ActionResult.UserExit;
138 + }
139 + catch (Exception ex)
140 + {
141 + session.Log("Exception thrown by custom action:");
142 + session.Log(ex.ToString());
143 + return (int) ActionResult.Failure;
144 + }
145 + }
146 +
147 + /// <summary>
148 + /// Checks the "MMsiBreak" environment variable for any matching custom action names.
149 + /// </summary>
150 + /// <param name="names">List of names to search for in the environment
151 + /// variable string.</param>
152 + /// <returns>True if a match was found, else false.</returns>
153 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
154 + internal static bool DebugBreakEnabled(string[] names)
155 + {
156 + string mmsibreak = Environment.GetEnvironmentVariable("MMsiBreak");
157 + if (mmsibreak != null)
158 + {
159 + foreach (string breakName in mmsibreak.Split(',', ';'))
160 + {
161 + foreach (string name in names)
162 + {
163 + if (breakName == name)
164 + {
165 + return true;
166 + }
167 + }
168 + }
169 + }
170 + return false;
171 + }
172 +
173 + /// <summary>
174 + /// Locates and parses an entrypoint mapping in CustomAction.config.
175 + /// </summary>
176 + /// <param name="session">Installer session handle, just used for logging.</param>
177 + /// <param name="entryPoint">Custom action entrypoint name: the key value
178 + /// in an item in the <c>customActions</c> section of the config file.</param>
179 + /// <param name="assemblyName">Returned display name of the assembly from
180 + /// the entrypoint mapping.</param>
181 + /// <param name="className">Returned class name of the entrypoint mapping.</param>
182 + /// <param name="methodName">Returned method name of the entrypoint mapping.</param>
183 + /// <returns>True if the entrypoint was found, false if not or if some error
184 + /// occurred.</returns>
185 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
186 + private static bool FindEntryPoint(
187 + Session session,
188 + string entryPoint,
189 + out string assemblyName,
190 + out string className,
191 + out string methodName)
192 + {
193 + assemblyName = null;
194 + className = null;
195 + methodName = null;
196 +
197 + string fullEntryPoint;
198 + if (entryPoint.IndexOf('!') > 0)
199 + {
200 + fullEntryPoint = entryPoint;
201 + }
202 + else
203 + {
204 +#if NET20
205 + IDictionary config;
206 + try
207 + {
208 + config = (IDictionary) ConfigurationManager.GetSection("customActions");
209 + }
210 + catch (ConfigurationException cex)
211 + {
212 + session.Log("Error: missing or invalid customActions config section.");
213 + session.Log(cex.ToString());
214 + return false;
215 + }
216 + fullEntryPoint = (string) config[entryPoint];
217 + if (fullEntryPoint == null)
218 + {
219 + session.Log(
220 + "Error: custom action entry point '{0}' not found " +
221 + "in customActions config section.",
222 + entryPoint);
223 + return false;
224 + }
225 +#else
226 + throw new NotImplementedException();
227 +#endif
228 + }
229 +
230 + int assemblySplit = fullEntryPoint.IndexOf('!');
231 + int methodSplit = fullEntryPoint.LastIndexOf('.');
232 + if (assemblySplit < 0 || methodSplit < 0 || methodSplit < assemblySplit)
233 + {
234 + session.Log("Error: invalid custom action entry point:" + entryPoint);
235 + return false;
236 + }
237 +
238 + assemblyName = fullEntryPoint.Substring(0, assemblySplit);
239 + className = fullEntryPoint.Substring(assemblySplit + 1, methodSplit - assemblySplit - 1);
240 + methodName = fullEntryPoint.Substring(methodSplit + 1);
241 + return true;
242 + }
243 +
244 + /// <summary>
245 + /// Uses reflection to load the assembly and class and find the method.
246 + /// </summary>
247 + /// <param name="session">Installer session handle, just used for logging.</param>
248 + /// <param name="assemblyName">Display name of the assembly containing the
249 + /// custom action method.</param>
250 + /// <param name="className">Fully-qualified name of the class containing the
251 + /// custom action method.</param>
252 + /// <param name="methodName">Name of the custom action method.</param>
253 + /// <returns>The method, or null if not found.</returns>
254 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
255 + private static MethodInfo GetCustomActionMethod(
256 + Session session,
257 + string assemblyName,
258 + string className,
259 + string methodName)
260 + {
261 + Assembly customActionAssembly;
262 + Type customActionClass = null;
263 + Exception caughtEx = null;
264 + try
265 + {
266 + customActionAssembly = AppDomain.CurrentDomain.Load(assemblyName);
267 + customActionClass = customActionAssembly.GetType(className, true, true);
268 + }
269 + catch (IOException ex) { caughtEx = ex; }
270 + catch (BadImageFormatException ex) { caughtEx = ex; }
271 + catch (TypeLoadException ex) { caughtEx = ex; }
272 + catch (ReflectionTypeLoadException ex) { caughtEx = ex; }
273 + catch (SecurityException ex) { caughtEx = ex; }
274 + if (caughtEx != null)
275 + {
276 + session.Log("Error: could not load custom action class " + className + " from assembly: " + assemblyName);
277 + session.Log(caughtEx.ToString());
278 + return null;
279 + }
280 +
281 + MethodInfo[] methods = customActionClass.GetMethods(
282 + BindingFlags.Public | BindingFlags.Static);
283 + foreach (MethodInfo method in methods)
284 + {
285 + if (method.Name == methodName &&
286 + CustomActionProxy.MethodHasCustomActionSignature(method))
287 + {
288 + return method;
289 + }
290 + }
291 + session.Log("Error: custom action method \"" + methodName +
292 + "\" is missing or has the wrong signature.");
293 + return null;
294 + }
295 +
296 + /// <summary>
297 + /// Checks if a method has the right return and paramater types
298 + /// for a custom action, and that it is marked by a CustomActionAttribute.
299 + /// </summary>
300 + /// <param name="method">Method to be checked.</param>
301 + /// <returns>True if the method is a valid custom action, else false.</returns>
302 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
303 + private static bool MethodHasCustomActionSignature(MethodInfo method)
304 + {
305 + if (method.ReturnType == typeof(ActionResult) &&
306 + method.GetParameters().Length == 1 &&
307 + method.GetParameters()[0].ParameterType == typeof(Session))
308 + {
309 + object[] methodAttribs = method.GetCustomAttributes(false);
310 + foreach (object attrib in methodAttribs)
311 + {
312 + if (attrib is CustomActionAttribute)
313 + {
314 + return true;
315 + }
316 + }
317 + }
318 + return false;
319 + }
320 + }
321 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Database.cs new
+933
@@ -0,0 +1,933 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// Accesses a Windows Installer database.
13 + /// </summary>
14 + /// <remarks><p>
15 + /// The <see cref="Commit"/> method must be called before the Database is closed to write out all
16 + /// persistent changes. If the Commit method is not called, the installer performs an implicit
17 + /// rollback upon object destruction.
18 + /// </p><p>
19 + /// The client can use the following procedure for data access:<list type="number">
20 + /// <item><description>Obtain a Database object using one of the Database constructors.</description></item>
21 + /// <item><description>Initiate a query using a SQL string by calling the <see cref="OpenView"/>
22 + /// method of the Database.</description></item>
23 + /// <item><description>Set query parameters in a <see cref="Record"/> and execute the database
24 + /// query by calling the <see cref="View.Execute(Record)"/> method of the <see cref="View"/>. This
25 + /// produces a result that can be fetched or updated.</description></item>
26 + /// <item><description>Call the <see cref="View.Fetch"/> method of the View repeatedly to return
27 + /// Records.</description></item>
28 + /// <item><description>Update database rows of a Record object obtained by the Fetch method using
29 + /// one of the <see cref="View.Modify"/> methods of the View.</description></item>
30 + /// <item><description>Release the query and any unfetched records by calling the <see cref="InstallerHandle.Close"/>
31 + /// method of the View.</description></item>
32 + /// <item><description>Persist any database updates by calling the Commit method of the Database.
33 + /// </description></item>
34 + /// </list>
35 + /// </p></remarks>
36 + public partial class Database : InstallerHandle
37 + {
38 + private string filePath;
39 + private DatabaseOpenMode openMode;
40 + private SummaryInfo summaryInfo;
41 + private TableCollection tables;
42 + private IList<string> deleteOnClose;
43 +
44 + /// <summary>
45 + /// Opens an existing database in read-only mode.
46 + /// </summary>
47 + /// <param name="filePath">Path to the database file.</param>
48 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
49 + /// <remarks><p>
50 + /// Because this constructor initiates database access, it cannot be used with a
51 + /// running installation.
52 + /// </p><p>
53 + /// The Database object should be <see cref="InstallerHandle.Close"/>d after use.
54 + /// It is best that the handle be closed manually as soon as it is no longer
55 + /// needed, as leaving lots of unused handles open can degrade performance.
56 + /// </p><p>
57 + /// Win32 MSI API:
58 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
59 + /// </p></remarks>
60 + public Database(string filePath)
61 + : this(filePath, DatabaseOpenMode.ReadOnly)
62 + {
63 + }
64 +
65 + /// <summary>
66 + /// Opens an existing database with another database as output.
67 + /// </summary>
68 + /// <param name="filePath">Path to the database to be read.</param>
69 + /// <param name="outputPath">Open mode for the database</param>
70 + /// <returns>Database object representing the created or opened database</returns>
71 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
72 + /// <remarks><p>
73 + /// When a database is opened as the output of another database, the summary information stream
74 + /// of the output database is actually a read-only mirror of the original database and thus cannot
75 + /// be changed. Additionally, it is not persisted with the database. To create or modify the
76 + /// summary information for the output database it must be closed and re-opened.
77 + /// </p><p>
78 + /// The Database object should be <see cref="InstallerHandle.Close"/>d after use.
79 + /// It is best that the handle be closed manually as soon as it is no longer
80 + /// needed, as leaving lots of unused handles open can degrade performance.
81 + /// </p><p>
82 + /// The database is opened in <see cref="DatabaseOpenMode.CreateDirect" /> mode, and will be
83 + /// automatically commited when it is closed.
84 + /// </p><p>
85 + /// Win32 MSI API:
86 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
87 + /// </p></remarks>
88 + public Database(string filePath, string outputPath)
89 + : this((IntPtr) Database.Open(filePath, outputPath), true, outputPath, DatabaseOpenMode.CreateDirect)
90 + {
91 + }
92 +
93 + /// <summary>
94 + /// Opens an existing database or creates a new one.
95 + /// </summary>
96 + /// <param name="filePath">Path to the database file. If an empty string
97 + /// is supplied, a temporary database is created that is not persisted.</param>
98 + /// <param name="mode">Open mode for the database</param>
99 + /// <exception cref="InstallerException">the database could not be created/opened</exception>
100 + /// <remarks><p>
101 + /// Because this constructor initiates database access, it cannot be used with a
102 + /// running installation.
103 + /// </p><p>
104 + /// The database object should be <see cref="InstallerHandle.Close"/>d after use.
105 + /// The finalizer will close the handle if it is still open, however due to the nondeterministic
106 + /// nature of finalization it is best that the handle be closed manually as soon as it is no
107 + /// longer needed, as leaving lots of unused handles open can degrade performance.
108 + /// </p><p>
109 + /// A database opened in <see cref="DatabaseOpenMode.CreateDirect" /> or
110 + /// <see cref="DatabaseOpenMode.Direct" /> mode will be automatically commited when it is
111 + /// closed. However a database opened in <see cref="DatabaseOpenMode.Create" /> or
112 + /// <see cref="DatabaseOpenMode.Transact" /> mode must have the <see cref="Commit" /> method
113 + /// called before it is closed, otherwise no changes will be persisted.
114 + /// </p><p>
115 + /// Win32 MSI API:
116 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopendatabase.asp">MsiOpenDatabase</a>
117 + /// </p></remarks>
118 + public Database(string filePath, DatabaseOpenMode mode)
119 + : this((IntPtr) Database.Open(filePath, mode), true, filePath, mode)
120 + {
121 + }
122 +
123 + /// <summary>
124 + /// Creates a new database from an MSI handle.
125 + /// </summary>
126 + /// <param name="handle">Native MSI database handle.</param>
127 + /// <param name="ownsHandle">True if the handle should be closed
128 + /// when the database object is disposed</param>
129 + /// <param name="filePath">Path of the database file, if known</param>
130 + /// <param name="openMode">Mode the handle was originally opened in</param>
131 + protected internal Database(
132 + IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode)
133 + : base(handle, ownsHandle)
134 + {
135 + this.filePath = filePath;
136 + this.openMode = openMode;
137 + }
138 +
139 + /// <summary>
140 + /// Gets the file path the Database was originally opened from, or null if not known.
141 + /// </summary>
142 + public String FilePath
143 + {
144 + get
145 + {
146 + return this.filePath;
147 + }
148 + }
149 +
150 + /// <summary>
151 + /// Gets the open mode for the database.
152 + /// </summary>
153 + public DatabaseOpenMode OpenMode
154 + {
155 + get
156 + {
157 + return this.openMode;
158 + }
159 + }
160 +
161 + /// <summary>
162 + /// Gets a boolean value indicating whether this database was opened in read-only mode.
163 + /// </summary>
164 + /// <remarks><p>
165 + /// Win32 MSI API:
166 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetdatabasestate.asp">MsiGetDatabaseState</a>
167 + /// </p></remarks>
168 + public bool IsReadOnly
169 + {
170 + get
171 + {
172 + if (RemotableNativeMethods.RemotingEnabled)
173 + {
174 + return true;
175 + }
176 +
177 + int state = NativeMethods.MsiGetDatabaseState((int) this.Handle);
178 + return state != 1;
179 + }
180 + }
181 +
182 + /// <summary>
183 + /// Gets the collection of tables in the Database.
184 + /// </summary>
185 + public TableCollection Tables
186 + {
187 + get
188 + {
189 + if (this.tables == null)
190 + {
191 + this.tables = new TableCollection(this);
192 + }
193 + return this.tables;
194 + }
195 + }
196 +
197 + /// <summary>
198 + /// Gets or sets the code page of the Database.
199 + /// </summary>
200 + /// <exception cref="IOException">error exporting/importing the codepage data</exception>
201 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
202 + /// <remarks><p>
203 + /// Getting or setting the code page is a slow operation because it involves an export or import
204 + /// of the codepage data to/from a temporary file.
205 + /// </p></remarks>
206 + public int CodePage
207 + {
208 + get
209 + {
210 + string tempFile = Path.GetTempFileName();
211 + StreamReader reader = null;
212 + try
213 + {
214 + this.Export("_ForceCodepage", tempFile);
215 + reader = File.OpenText(tempFile);
216 + reader.ReadLine(); // Skip column name record.
217 + reader.ReadLine(); // Skip column defn record.
218 + string codePageLine = reader.ReadLine();
219 + return Int32.Parse(codePageLine.Split('\t')[0], CultureInfo.InvariantCulture.NumberFormat);
220 + }
221 + finally
222 + {
223 + if (reader != null) reader.Close();
224 + File.Delete(tempFile);
225 + }
226 + }
227 +
228 + set
229 + {
230 + string tempFile = Path.GetTempFileName();
231 + StreamWriter writer = null;
232 + try
233 + {
234 + writer = File.AppendText(tempFile);
235 + writer.WriteLine("");
236 + writer.WriteLine("");
237 + writer.WriteLine("{0}\t_ForceCodepage", value);
238 + writer.Close();
239 + writer = null;
240 + this.Import(tempFile);
241 + }
242 + finally
243 + {
244 + if (writer != null) writer.Close();
245 + File.Delete(tempFile);
246 + }
247 + }
248 + }
249 +
250 + /// <summary>
251 + /// Gets the SummaryInfo object for this database that can be used to examine and modify properties
252 + /// to the summary information stream.
253 + /// </summary>
254 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
255 + /// <remarks><p>
256 + /// The object returned from this property does not need to be explicitly persisted or closed.
257 + /// Any modifications will be automatically saved when the database is committed.
258 + /// </p><p>
259 + /// Win32 MSI API:
260 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a>
261 + /// </p></remarks>
262 + public SummaryInfo SummaryInfo
263 + {
264 + get
265 + {
266 + if (this.summaryInfo == null || this.summaryInfo.IsClosed)
267 + {
268 + lock (this.Sync)
269 + {
270 + if (this.summaryInfo == null || this.summaryInfo.IsClosed)
271 + {
272 + int summaryInfoHandle;
273 + int maxProperties = this.IsReadOnly ? 0 : SummaryInfo.MAX_PROPERTIES;
274 + uint ret = RemotableNativeMethods.MsiGetSummaryInformation((int) this.Handle, null, (uint) maxProperties, out summaryInfoHandle);
275 + if (ret != 0)
276 + {
277 + throw InstallerException.ExceptionFromReturnCode(ret);
278 + }
279 + this.summaryInfo = new SummaryInfo((IntPtr) summaryInfoHandle, true);
280 + }
281 + }
282 + }
283 + return this.summaryInfo;
284 + }
285 + }
286 +
287 + /// <summary>
288 + /// Creates a new Database object from an integer database handle.
289 + /// </summary>
290 + /// <remarks><p>
291 + /// This method is only provided for interop purposes. A Database object
292 + /// should normally be obtained from <see cref="Session.Database"/> or
293 + /// a public Database constructor.
294 + /// </p></remarks>
295 + /// <param name="handle">Integer database handle</param>
296 + /// <param name="ownsHandle">true to close the handle when this object is disposed</param>
297 + public static Database FromHandle(IntPtr handle, bool ownsHandle)
298 + {
299 + return new Database(
300 + handle,
301 + ownsHandle,
302 + null,
303 + NativeMethods.MsiGetDatabaseState((int) handle) == 1 ? DatabaseOpenMode.Direct : DatabaseOpenMode.ReadOnly);
304 + }
305 +
306 + /// <summary>
307 + /// Schedules a file or directory for deletion after the database handle is closed.
308 + /// </summary>
309 + /// <param name="path">File or directory path to be deleted. All files and subdirectories
310 + /// under a directory are deleted.</param>
311 + /// <remarks><p>
312 + /// Once an item is scheduled, it cannot be unscheduled.
313 + /// </p><p>
314 + /// The items cannot be deleted if the Database object is auto-disposed by the
315 + /// garbage collector; the handle must be explicitly closed.
316 + /// </p><p>
317 + /// Files which are read-only or otherwise locked cannot be deleted,
318 + /// but they will not cause an exception to be thrown.
319 + /// </p></remarks>
320 + public void DeleteOnClose(string path)
321 + {
322 + if (this.deleteOnClose == null)
323 + {
324 + this.deleteOnClose = new List<string>();
325 + }
326 + this.deleteOnClose.Add(path);
327 + }
328 +
329 + /// <summary>
330 + /// Merges another database with this database.
331 + /// </summary>
332 + /// <param name="otherDatabase">The database to be merged into this database</param>
333 + /// <param name="errorTable">Optional name of table to contain the names of the tables containing
334 + /// merge conflicts, the number of conflicting rows within the table, and a reference to the table
335 + /// with the merge conflict.</param>
336 + /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception>
337 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
338 + /// <remarks><p>
339 + /// Merge does not copy over embedded cabinet files or embedded transforms from the
340 + /// reference database into the target database. Embedded data streams that are listed in the
341 + /// Binary table or Icon table are copied from the reference database to the target database.
342 + /// Storage embedded in the reference database are not copied to the target database.
343 + /// </p><p>
344 + /// The Merge method merges the data of two databases. These databases must have the same
345 + /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists
346 + /// if the data in any row in the first database differs from the data in the corresponding row
347 + /// of the second database. Corresponding rows are in the same table of both databases and have
348 + /// the same primary key in both databases. The tables of non-conflicting databases must have
349 + /// the same number of primary keys, same number of columns, same column types, same column names,
350 + /// and the same data in rows with identical primary keys. Temporary columns however don't matter
351 + /// in the column count and corresponding tables can have a different number of temporary columns
352 + /// without creating conflict as long as the persistent columns match.
353 + /// </p><p>
354 + /// If the number, type, or name of columns in corresponding tables are different, the
355 + /// schema of the two databases are incompatible and the installer will stop processing tables
356 + /// and the merge fails. The installer checks that the two databases have the same schema before
357 + /// checking for row merge conflicts. If the schemas are incompatible, the databases have be
358 + /// modified.
359 + /// </p><p>
360 + /// If the data in particular rows differ, this is a row merge conflict, the merge fails
361 + /// and creates a new table with the specified name. The first column of this table is the name
362 + /// of the table having the conflict. The second column gives the number of rows in the table
363 + /// having the conflict.
364 + /// </p><p>
365 + /// Win32 MSI API:
366 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a>
367 + /// </p></remarks>
368 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
369 + public void Merge(Database otherDatabase, string errorTable)
370 + {
371 + if (otherDatabase == null)
372 + {
373 + throw new ArgumentNullException("otherDatabase");
374 + }
375 +
376 + uint ret = NativeMethods.MsiDatabaseMerge((int) this.Handle, (int) otherDatabase.Handle, errorTable);
377 + if (ret != 0)
378 + {
379 + if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
380 + {
381 + throw new MergeException(this, errorTable);
382 + }
383 + else if (ret == (uint) NativeMethods.Error.DATATYPE_MISMATCH)
384 + {
385 + throw new MergeException("Schema difference between the two databases.");
386 + }
387 + else
388 + {
389 + throw InstallerException.ExceptionFromReturnCode(ret);
390 + }
391 + }
392 + }
393 +
394 + /// <summary>
395 + /// Merges another database with this database.
396 + /// </summary>
397 + /// <param name="otherDatabase">The database to be merged into this database</param>
398 + /// <exception cref="MergeException">merge failed due to a schema difference or data conflict</exception>
399 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
400 + /// <remarks><p>
401 + /// MsiDatabaseMerge does not copy over embedded cabinet files or embedded transforms from
402 + /// the reference database into the target database. Embedded data streams that are listed in
403 + /// the Binary table or Icon table are copied from the reference database to the target database.
404 + /// Storage embedded in the reference database are not copied to the target database.
405 + /// </p><p>
406 + /// The Merge method merges the data of two databases. These databases must have the same
407 + /// codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists
408 + /// if the data in any row in the first database differs from the data in the corresponding row
409 + /// of the second database. Corresponding rows are in the same table of both databases and have
410 + /// the same primary key in both databases. The tables of non-conflicting databases must have
411 + /// the same number of primary keys, same number of columns, same column types, same column names,
412 + /// and the same data in rows with identical primary keys. Temporary columns however don't matter
413 + /// in the column count and corresponding tables can have a different number of temporary columns
414 + /// without creating conflict as long as the persistent columns match.
415 + /// </p><p>
416 + /// If the number, type, or name of columns in corresponding tables are different, the
417 + /// schema of the two databases are incompatible and the installer will stop processing tables
418 + /// and the merge fails. The installer checks that the two databases have the same schema before
419 + /// checking for row merge conflicts. If the schemas are incompatible, the databases have be
420 + /// modified.
421 + /// </p><p>
422 + /// Win32 MSI API:
423 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasemerge.asp">MsiDatabaseMerge</a>
424 + /// </p></remarks>
425 + public void Merge(Database otherDatabase) { this.Merge(otherDatabase, null); }
426 +
427 + /// <summary>
428 + /// Checks whether a table exists and is persistent in the database.
429 + /// </summary>
430 + /// <param name="table">The table to the checked</param>
431 + /// <returns>true if the table exists and is persistent in the database; false otherwise</returns>
432 + /// <exception cref="ArgumentException">the table is unknown</exception>
433 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
434 + /// <remarks><p>
435 + /// To check whether a table exists regardless of persistence,
436 + /// use <see cref="TableCollection.Contains"/>.
437 + /// </p><p>
438 + /// Win32 MSI API:
439 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseistablepersistent.asp">MsiDatabaseIsTablePersistent</a>
440 + /// </p></remarks>
441 + public bool IsTablePersistent(string table)
442 + {
443 + if (String.IsNullOrEmpty(table))
444 + {
445 + throw new ArgumentNullException("table");
446 + }
447 + uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.Handle, table);
448 + if (ret == 3) // MSICONDITION_ERROR
449 + {
450 + throw new InstallerException();
451 + }
452 + return ret == 1;
453 + }
454 +
455 + /// <summary>
456 + /// Checks whether a table contains a persistent column with a given name.
457 + /// </summary>
458 + /// <param name="table">The table to the checked</param>
459 + /// <param name="column">The name of the column to be checked</param>
460 + /// <returns>true if the column exists in the table; false if the column is temporary or does not exist.</returns>
461 + /// <exception cref="InstallerException">the View could not be executed</exception>
462 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
463 + /// <remarks><p>
464 + /// To check whether a column exists regardless of persistence,
465 + /// use <see cref="ColumnCollection.Contains"/>.
466 + /// </p></remarks>
467 + public bool IsColumnPersistent(string table, string column)
468 + {
469 + if (String.IsNullOrEmpty(table))
470 + {
471 + throw new ArgumentNullException("table");
472 + }
473 + if (String.IsNullOrEmpty(column))
474 + {
475 + throw new ArgumentNullException("column");
476 + }
477 + using (View view = this.OpenView(
478 + "SELECT `Number` FROM `_Columns` WHERE `Table` = '{0}' AND `Name` = '{1}'", table, column))
479 + {
480 + view.Execute();
481 + using (Record rec = view.Fetch())
482 + {
483 + return (rec != null);
484 + }
485 + }
486 + }
487 +
488 + /// <summary>
489 + /// Gets the count of all rows in the table.
490 + /// </summary>
491 + /// <param name="table">Name of the table whose rows are to be counted</param>
492 + /// <returns>The count of all rows in the table</returns>
493 + /// <exception cref="InstallerException">the View could not be executed</exception>
494 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
495 + public int CountRows(string table)
496 + {
497 + return this.CountRows(table, null);
498 + }
499 +
500 + /// <summary>
501 + /// Gets the count of all rows in the table that satisfy a given condition.
502 + /// </summary>
503 + /// <param name="table">Name of the table whose rows are to be counted</param>
504 + /// <param name="where">Conditional expression, such as could be placed on the end of a SQL WHERE clause</param>
505 + /// <returns>The count of all rows in the table satisfying the condition</returns>
506 + /// <exception cref="BadQuerySyntaxException">the SQL WHERE syntax is invalid</exception>
507 + /// <exception cref="InstallerException">the View could not be executed</exception>
508 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
509 + public int CountRows(string table, string where)
510 + {
511 + if (String.IsNullOrEmpty(table))
512 + {
513 + throw new ArgumentNullException("table");
514 + }
515 +
516 + // to support temporary tables like _Streams, run the query even if the table isn't persistent
517 + TableInfo tableInfo = this.Tables[table];
518 + string primaryKeys = tableInfo == null ? "*" : String.Concat("`", tableInfo.PrimaryKeys[0], "`");
519 + int count;
520 +
521 + try
522 + {
523 + using (View view = this.OpenView(
524 + "SELECT {0} FROM `{1}`{2}",
525 + primaryKeys,
526 + table,
527 + (where != null && where.Length != 0 ? " WHERE " + where : "")))
528 + {
529 + view.Execute();
530 + for (count = 0; ; count++)
531 + {
532 + // Avoid creating unnecessary Record objects by not calling View.Fetch().
533 + int recordHandle;
534 + uint ret = RemotableNativeMethods.MsiViewFetch((int)view.Handle, out recordHandle);
535 + if (ret == (uint)NativeMethods.Error.NO_MORE_ITEMS)
536 + {
537 + break;
538 + }
539 +
540 + if (ret != 0)
541 + {
542 + throw InstallerException.ExceptionFromReturnCode(ret);
543 + }
544 +
545 + RemotableNativeMethods.MsiCloseHandle(recordHandle);
546 + }
547 + }
548 + }
549 + catch (BadQuerySyntaxException)
550 + {
551 + // table was missing
552 + count = 0;
553 + }
554 +
555 + return count;
556 + }
557 +
558 + /// <summary>
559 + /// Finalizes the persistent form of the database. All persistent data is written
560 + /// to the writeable database, and no temporary columns or rows are written.
561 + /// </summary>
562 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
563 + /// <remarks><p>
564 + /// For a database open in <see cref="DatabaseOpenMode.ReadOnly"/> mode, this method has no effect.
565 + /// </p><p>
566 + /// For a database open in <see cref="DatabaseOpenMode.CreateDirect" /> or <see cref="DatabaseOpenMode.Direct" />
567 + /// mode, it is not necessary to call this method because the database will be automatically committed
568 + /// when it is closed. However this method may be called at any time to persist the current state of tables
569 + /// loaded into memory.
570 + /// </p><p>
571 + /// For a database open in <see cref="DatabaseOpenMode.Create" /> or <see cref="DatabaseOpenMode.Transact" />
572 + /// mode, no changes will be persisted until this method is called. If the database object is closed without
573 + /// calling this method, the database file remains unmodified.
574 + /// </p><p>
575 + /// Win32 MSI API:
576 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasecommit.asp">MsiDatabaseCommit</a>
577 + /// </p></remarks>
578 + public void Commit()
579 + {
580 + if (this.summaryInfo != null && !this.summaryInfo.IsClosed)
581 + {
582 + this.summaryInfo.Persist();
583 + this.summaryInfo.Close();
584 + this.summaryInfo = null;
585 + }
586 + uint ret = NativeMethods.MsiDatabaseCommit((int) this.Handle);
587 + if (ret != 0)
588 + {
589 + throw InstallerException.ExceptionFromReturnCode(ret);
590 + }
591 + }
592 +
593 + /// <summary>
594 + /// Copies the structure and data from a specified table to a text archive file.
595 + /// </summary>
596 + /// <param name="table">Name of the table to be exported</param>
597 + /// <param name="exportFilePath">Path to the file to be created</param>
598 + /// <exception cref="FileNotFoundException">the file path is invalid</exception>
599 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
600 + /// <remarks><p>
601 + /// Win32 MSI API:
602 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a>
603 + /// </p></remarks>
604 + public void Export(string table, string exportFilePath)
605 + {
606 + if (table == null)
607 + {
608 + throw new ArgumentNullException("table");
609 + }
610 +
611 + FileInfo file = new FileInfo(exportFilePath);
612 + uint ret = NativeMethods.MsiDatabaseExport((int) this.Handle, table, file.DirectoryName, file.Name);
613 + if (ret != 0)
614 + {
615 + if (ret == (uint) NativeMethods.Error.BAD_PATHNAME)
616 + {
617 + throw new FileNotFoundException(null, exportFilePath);
618 + }
619 + else
620 + {
621 + throw InstallerException.ExceptionFromReturnCode(ret);
622 + }
623 + }
624 + }
625 +
626 + /// <summary>
627 + /// Imports a database table from a text archive file, dropping any existing table.
628 + /// </summary>
629 + /// <param name="importFilePath">Path to the file to be imported.
630 + /// The table name is specified within the file.</param>
631 + /// <exception cref="FileNotFoundException">the file path is invalid</exception>
632 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
633 + /// <remarks><p>
634 + /// Win32 MSI API:
635 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a>
636 + /// </p></remarks>
637 + public void Import(string importFilePath)
638 + {
639 + if (String.IsNullOrEmpty(importFilePath))
640 + {
641 + throw new ArgumentNullException("importFilePath");
642 + }
643 +
644 + FileInfo file = new FileInfo(importFilePath);
645 + uint ret = NativeMethods.MsiDatabaseImport((int) this.Handle, file.DirectoryName, file.Name);
646 + if (ret != 0)
647 + {
648 + if (ret == (uint) NativeMethods.Error.BAD_PATHNAME)
649 + {
650 + throw new FileNotFoundException(null, importFilePath);
651 + }
652 + else
653 + {
654 + throw InstallerException.ExceptionFromReturnCode(ret);
655 + }
656 + }
657 + }
658 +
659 + /// <summary>
660 + /// Exports all database tables, streams, and summary information to archive files.
661 + /// </summary>
662 + /// <param name="directoryPath">Path to the directory where archive files will be created</param>
663 + /// <exception cref="FileNotFoundException">the directory path is invalid</exception>
664 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
665 + /// <remarks><p>
666 + /// The directory will be created if it does not already exist.
667 + /// </p><p>
668 + /// Win32 MSI API:
669 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseexport.asp">MsiDatabaseExport</a>
670 + /// </p></remarks>
671 + public void ExportAll(string directoryPath)
672 + {
673 + if (String.IsNullOrEmpty(directoryPath))
674 + {
675 + throw new ArgumentNullException("directoryPath");
676 + }
677 +
678 + if (!Directory.Exists(directoryPath))
679 + {
680 + Directory.CreateDirectory(directoryPath);
681 + }
682 +
683 + this.Export("_SummaryInformation", Path.Combine(directoryPath, "_SummaryInformation.idt"));
684 +
685 + using (View view = this.OpenView("SELECT `Name` FROM `_Tables`"))
686 + {
687 + view.Execute();
688 +
689 + foreach (Record rec in view) using (rec)
690 + {
691 + string table = (string) rec[1];
692 +
693 + this.Export(table, Path.Combine(directoryPath, table + ".idt"));
694 + }
695 + }
696 +
697 + if (!Directory.Exists(Path.Combine(directoryPath, "_Streams")))
698 + {
699 + Directory.CreateDirectory(Path.Combine(directoryPath, "_Streams"));
700 + }
701 +
702 + using (View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`"))
703 + {
704 + view.Execute();
705 +
706 + foreach (Record rec in view) using (rec)
707 + {
708 + string stream = (string) rec[1];
709 + if (stream.EndsWith("SummaryInformation", StringComparison.Ordinal)) continue;
710 +
711 + int i = stream.IndexOf('.');
712 + if (i >= 0)
713 + {
714 + if (File.Exists(Path.Combine(
715 + directoryPath,
716 + Path.Combine(stream.Substring(0, i), stream.Substring(i + 1) + ".ibd"))))
717 + {
718 + continue;
719 + }
720 + }
721 + rec.GetStream(2, Path.Combine(directoryPath, Path.Combine("_Streams", stream)));
722 + }
723 + }
724 + }
725 +
726 + /// <summary>
727 + /// Imports all database tables, streams, and summary information from archive files.
728 + /// </summary>
729 + /// <param name="directoryPath">Path to the directory from which archive files will be imported</param>
730 + /// <exception cref="FileNotFoundException">the directory path is invalid</exception>
731 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
732 + /// <remarks><p>
733 + /// Win32 MSI API:
734 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseimport.asp">MsiDatabaseImport</a>
735 + /// </p></remarks>
736 + public void ImportAll(string directoryPath)
737 + {
738 + if (String.IsNullOrEmpty(directoryPath))
739 + {
740 + throw new ArgumentNullException("directoryPath");
741 + }
742 +
743 + if (File.Exists(Path.Combine(directoryPath, "_SummaryInformation.idt")))
744 + {
745 + this.Import(Path.Combine(directoryPath, "_SummaryInformation.idt"));
746 + }
747 +
748 + string[] idtFiles = Directory.GetFiles(directoryPath, "*.idt");
749 + foreach (string file in idtFiles)
750 + {
751 + if (Path.GetFileName(file) != "_SummaryInformation.idt")
752 + {
753 + this.Import(file);
754 + }
755 + }
756 +
757 + if (Directory.Exists(Path.Combine(directoryPath, "_Streams")))
758 + {
759 + View view = this.OpenView("SELECT `Name`, `Data` FROM `_Streams`");
760 + Record rec = null;
761 + try
762 + {
763 + view.Execute();
764 + string[] streamFiles = Directory.GetFiles(Path.Combine(directoryPath, "_Streams"));
765 + foreach (string file in streamFiles)
766 + {
767 + rec = this.CreateRecord(2);
768 + rec[1] = Path.GetFileName(file);
769 + rec.SetStream(2, file);
770 + view.Insert(rec);
771 + rec.Close();
772 + rec = null;
773 + }
774 + }
775 + finally
776 + {
777 + if (rec != null) rec.Close();
778 + view.Close();
779 + }
780 + }
781 + }
782 +
783 + /// <summary>
784 + /// Creates a new record object with the requested number of fields.
785 + /// </summary>
786 + /// <param name="fieldCount">Required number of fields, which may be 0.
787 + /// The maximum number of fields in a record is limited to 65535.</param>
788 + /// <returns>A new record object that can be used with the database.</returns>
789 + /// <remarks><p>
790 + /// This method is equivalent to directly calling the <see cref="Record" />
791 + /// constructor in all cases outside of a custom action context. When in a
792 + /// custom action session, this method allows creation of a record that can
793 + /// work with a database other than the session database.
794 + /// </p><p>
795 + /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
796 + /// It is best that the handle be closed manually as soon as it is no longer
797 + /// needed, as leaving lots of unused handles open can degrade performance.
798 + /// </p><p>
799 + /// Win32 MSI API:
800 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
801 + /// </p></remarks>
802 + public Record CreateRecord(int fieldCount)
803 + {
804 + int hRecord = RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, (int) this.Handle);
805 + return new Record((IntPtr) hRecord, true, (View) null);
806 + }
807 +
808 + /// <summary>
809 + /// Returns the file path of this database, or the handle value if a file path was not specified.
810 + /// </summary>
811 + public override string ToString()
812 + {
813 + if (this.FilePath != null)
814 + {
815 + return this.FilePath;
816 + }
817 + else
818 + {
819 + return "#" + ((int) this.Handle).ToString(CultureInfo.InvariantCulture);
820 + }
821 + }
822 +
823 + /// <summary>
824 + /// Closes the database handle. After closing a handle, further method calls may throw <see cref="InvalidHandleException"/>.
825 + /// </summary>
826 + /// <param name="disposing">If true, the method has been called directly or
827 + /// indirectly by a user's code, so managed and unmanaged resources will be
828 + /// disposed. If false, only unmanaged resources will be disposed.</param>
829 + protected override void Dispose(bool disposing)
830 + {
831 + if (!this.IsClosed &&
832 + (this.OpenMode == DatabaseOpenMode.CreateDirect ||
833 + this.OpenMode == DatabaseOpenMode.Direct))
834 + {
835 + // Always commit a direct-opened database before closing.
836 + // This avoids unexpected corruption of the database.
837 + this.Commit();
838 + }
839 +
840 + base.Dispose(disposing);
841 +
842 + if (disposing)
843 + {
844 + if (this.summaryInfo != null)
845 + {
846 + this.summaryInfo.Close();
847 + this.summaryInfo = null;
848 + }
849 +
850 + if (this.deleteOnClose != null)
851 + {
852 + foreach (string path in this.deleteOnClose)
853 + {
854 + try
855 + {
856 + if (Directory.Exists(path))
857 + {
858 + Directory.Delete(path, true);
859 + }
860 + else
861 + {
862 + if (File.Exists(path)) File.Delete(path);
863 + }
864 + }
865 + catch (IOException)
866 + {
867 + }
868 + catch (UnauthorizedAccessException)
869 + {
870 + }
871 + }
872 + this.deleteOnClose = null;
873 + }
874 + }
875 + }
876 +
877 + private static int Open(string filePath, string outputPath)
878 + {
879 + if (String.IsNullOrEmpty(filePath))
880 + {
881 + throw new ArgumentNullException("filePath");
882 + }
883 +
884 + if (String.IsNullOrEmpty(outputPath))
885 + {
886 + throw new ArgumentNullException("outputPath");
887 + }
888 +
889 + int hDb;
890 + uint ret = NativeMethods.MsiOpenDatabase(filePath, outputPath, out hDb);
891 + if (ret != 0)
892 + {
893 + throw InstallerException.ExceptionFromReturnCode(ret);
894 + }
895 + return hDb;
896 + }
897 +
898 + private static int Open(string filePath, DatabaseOpenMode mode)
899 + {
900 + if (String.IsNullOrEmpty(filePath))
901 + {
902 + throw new ArgumentNullException("filePath");
903 + }
904 +
905 + if (Path.GetExtension(filePath).Equals(".msp", StringComparison.Ordinal))
906 + {
907 + const int DATABASEOPENMODE_PATCH = 32;
908 + int patchMode = (int) mode | DATABASEOPENMODE_PATCH;
909 + mode = (DatabaseOpenMode) patchMode;
910 + }
911 +
912 + int hDb;
913 + uint ret = NativeMethods.MsiOpenDatabase(filePath, (IntPtr) mode, out hDb);
914 + if (ret != 0)
915 + {
916 + throw InstallerException.ExceptionFromReturnCode(
917 + ret,
918 + String.Format(CultureInfo.InvariantCulture, "Database=\"{0}\"", filePath));
919 + }
920 + return hDb;
921 + }
922 +
923 + /// <summary>
924 + /// Returns the value of the specified property.
925 + /// </summary>
926 + /// <param name="property">Name of the property to retrieve.</param>
927 + public string ExecutePropertyQuery(string property)
928 + {
929 + IList<string> values = this.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = '{0}'", property);
930 + return (values.Count > 0 ? values[0] : null);
931 + }
932 + }
933 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseQuery.cs new
+412
@@ -0,0 +1,412 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + public partial class Database
12 + {
13 + /// <summary>
14 + /// Gets a View object representing the query specified by a SQL string.
15 + /// </summary>
16 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
17 + /// <param name="args">Zero or more objects to format</param>
18 + /// <returns>A View object representing the query specified by a SQL string</returns>
19 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
20 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
21 + /// <remarks><p>
22 + /// The <paramref name="sqlFormat"/> parameter is formatted using <see cref="String.Format(string,object[])"/>.
23 + /// </p><p>
24 + /// The View object should be <see cref="InstallerHandle.Close"/>d after use.
25 + /// It is best that the handle be closed manually as soon as it is no longer
26 + /// needed, as leaving lots of unused handles open can degrade performance.
27 + /// </p><p>
28 + /// Win32 MSI API:
29 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>
30 + /// </p></remarks>
31 + public View OpenView(string sqlFormat, params object[] args)
32 + {
33 + if (String.IsNullOrEmpty(sqlFormat))
34 + {
35 + throw new ArgumentNullException("sqlFormat");
36 + }
37 +
38 + string sql = (args == null || args.Length == 0 ? sqlFormat :
39 + String.Format(CultureInfo.InvariantCulture, sqlFormat, args));
40 + int viewHandle;
41 + uint ret = RemotableNativeMethods.MsiDatabaseOpenView((int) this.Handle, sql, out viewHandle);
42 + if (ret != 0)
43 + {
44 + throw InstallerException.ExceptionFromReturnCode(ret);
45 + }
46 +
47 + return new View((IntPtr) viewHandle, sql, this);
48 + }
49 +
50 + /// <summary>
51 + /// Executes the query specified by a SQL string. The query may not be a SELECT statement.
52 + /// </summary>
53 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
54 + /// <param name="args">Zero or more objects to format</param>
55 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
56 + /// <exception cref="InstallerException">the View could not be executed</exception>
57 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
58 + /// <remarks><p>
59 + /// The <paramref name="sqlFormat"/> parameter is formatted using
60 + /// <see cref="String.Format(string,object[])"/>.
61 + /// </p><p>
62 + /// Win32 MSI APIs:
63 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
64 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
65 + /// </p></remarks>
66 + public void Execute(string sqlFormat, params object[] args)
67 + {
68 + if (String.IsNullOrEmpty(sqlFormat))
69 + {
70 + throw new ArgumentNullException("sqlFormat");
71 + }
72 +
73 + this.Execute(
74 + args == null || args.Length == 0 ?
75 + sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
76 + (Record) null);
77 + }
78 +
79 + /// <summary>
80 + /// Executes the query specified by a SQL string. The query may not be a SELECT statement.
81 + /// </summary>
82 + /// <param name="sql">SQL query string</param>
83 + /// <param name="record">Optional Record object containing the values that replace
84 + /// the parameter tokens (?) in the SQL query.</param>
85 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
86 + /// <exception cref="InstallerException">the View could not be executed</exception>
87 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
88 + /// <remarks><p>
89 + /// Win32 MSI APIs:
90 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
91 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
92 + /// </p></remarks>
93 + public void Execute(string sql, Record record)
94 + {
95 + if (String.IsNullOrEmpty(sql))
96 + {
97 + throw new ArgumentNullException("sql");
98 + }
99 +
100 + using (View view = this.OpenView(sql))
101 + {
102 + view.Execute(record);
103 + }
104 + }
105 +
106 + /// <summary>
107 + /// Executes the specified SQL SELECT query and returns all results.
108 + /// </summary>
109 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
110 + /// <param name="args">Zero or more objects to format</param>
111 + /// <returns>All results combined into an array</returns>
112 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
113 + /// <exception cref="InstallerException">the View could not be executed</exception>
114 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
115 + /// <remarks><p>
116 + /// The <paramref name="sqlFormat"/> parameter is formatted using
117 + /// <see cref="String.Format(string,object[])"/>.
118 + /// </p><p>
119 + /// Multiple rows columns will be collapsed into a single one-dimensional list.
120 + /// </p><p>
121 + /// Win32 MSI APIs:
122 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
123 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
124 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
125 + /// </p></remarks>
126 + public IList ExecuteQuery(string sqlFormat, params object[] args)
127 + {
128 + if (String.IsNullOrEmpty(sqlFormat))
129 + {
130 + throw new ArgumentNullException("sqlFormat");
131 + }
132 +
133 + return this.ExecuteQuery(
134 + args == null || args.Length == 0 ?
135 + sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
136 + (Record) null);
137 + }
138 +
139 + /// <summary>
140 + /// Executes the specified SQL SELECT query and returns all results.
141 + /// </summary>
142 + /// <param name="sql">SQL SELECT query string</param>
143 + /// <param name="record">Optional Record object containing the values that replace
144 + /// the parameter tokens (?) in the SQL query.</param>
145 + /// <returns>All results combined into an array</returns>
146 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
147 + /// <exception cref="InstallerException">the View could not be executed</exception>
148 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
149 + /// <remarks><p>
150 + /// Multiple rows columns will be collapsed into a single one-dimensional list.
151 + /// </p><p>
152 + /// Win32 MSI APIs:
153 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
154 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
155 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
156 + /// </p></remarks>
157 + public IList ExecuteQuery(string sql, Record record)
158 + {
159 + if (String.IsNullOrEmpty(sql))
160 + {
161 + throw new ArgumentNullException("sql");
162 + }
163 +
164 + using (View view = this.OpenView(sql))
165 + {
166 + view.Execute(record);
167 + IList results = new ArrayList();
168 + int fieldCount = 0;
169 +
170 + foreach (Record rec in view) using (rec)
171 + {
172 + if (fieldCount == 0) fieldCount = rec.FieldCount;
173 + for (int i = 1; i <= fieldCount; i++)
174 + {
175 + results.Add(rec[i]);
176 + }
177 + }
178 +
179 + return results;
180 + }
181 + }
182 +
183 + /// <summary>
184 + /// Executes the specified SQL SELECT query and returns all results as integers.
185 + /// </summary>
186 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
187 + /// <param name="args">Zero or more objects to format</param>
188 + /// <returns>All results combined into an array</returns>
189 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
190 + /// <exception cref="InstallerException">the View could not be executed</exception>
191 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
192 + /// <remarks><p>
193 + /// The <paramref name="sqlFormat"/> parameter is formatted using
194 + /// <see cref="String.Format(string,object[])"/>.
195 + /// </p><p>
196 + /// Multiple rows columns will be collapsed into a single one-dimensional list.
197 + /// </p><p>
198 + /// Win32 MSI APIs:
199 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
200 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
201 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
202 + /// </p></remarks>
203 + public IList<int> ExecuteIntegerQuery(string sqlFormat, params object[] args)
204 + {
205 + if (String.IsNullOrEmpty(sqlFormat))
206 + {
207 + throw new ArgumentNullException("sqlFormat");
208 + }
209 +
210 + return this.ExecuteIntegerQuery(
211 + args == null || args.Length == 0 ?
212 + sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
213 + (Record) null);
214 + }
215 +
216 + /// <summary>
217 + /// Executes the specified SQL SELECT query and returns all results as integers.
218 + /// </summary>
219 + /// <param name="sql">SQL SELECT query string</param>
220 + /// <param name="record">Optional Record object containing the values that replace
221 + /// the parameter tokens (?) in the SQL query.</param>
222 + /// <returns>All results combined into an array</returns>
223 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
224 + /// <exception cref="InstallerException">the View could not be executed</exception>
225 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
226 + /// <remarks><p>
227 + /// Multiple rows columns will be collapsed into a single one-dimensional list.
228 + /// </p><p>
229 + /// Win32 MSI APIs:
230 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
231 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
232 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
233 + /// </p></remarks>
234 + public IList<int> ExecuteIntegerQuery(string sql, Record record)
235 + {
236 + if (String.IsNullOrEmpty(sql))
237 + {
238 + throw new ArgumentNullException("sql");
239 + }
240 +
241 + using (View view = this.OpenView(sql))
242 + {
243 + view.Execute(record);
244 + IList<int> results = new List<int>();
245 + int fieldCount = 0;
246 +
247 + foreach (Record rec in view) using (rec)
248 + {
249 + if (fieldCount == 0) fieldCount = rec.FieldCount;
250 + for (int i = 1; i <= fieldCount; i++)
251 + {
252 + results.Add(rec.GetInteger(i));
253 + }
254 + }
255 +
256 + return results;
257 + }
258 + }
259 +
260 + /// <summary>
261 + /// Executes the specified SQL SELECT query and returns all results as strings.
262 + /// </summary>
263 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
264 + /// <param name="args">Zero or more objects to format</param>
265 + /// <returns>All results combined into an array</returns>
266 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
267 + /// <exception cref="InstallerException">the View could not be executed</exception>
268 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
269 + /// <remarks><p>
270 + /// The <paramref name="sqlFormat"/> parameter is formatted using
271 + /// <see cref="String.Format(string,object[])"/>.
272 + /// </p><p>
273 + /// Multiple rows columns will be collapsed into a single on-dimensional list.
274 + /// </p><p>
275 + /// Win32 MSI APIs:
276 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
277 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
278 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
279 + /// </p></remarks>
280 + public IList<string> ExecuteStringQuery(string sqlFormat, params object[] args)
281 + {
282 + if (String.IsNullOrEmpty(sqlFormat))
283 + {
284 + throw new ArgumentNullException("sqlFormat");
285 + }
286 +
287 + return this.ExecuteStringQuery(
288 + args == null || args.Length == 0 ?
289 + sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
290 + (Record) null);
291 + }
292 +
293 + /// <summary>
294 + /// Executes the specified SQL SELECT query and returns all results as strings.
295 + /// </summary>
296 + /// <param name="sql">SQL SELECT query string</param>
297 + /// <param name="record">Optional Record object containing the values that replace
298 + /// the parameter tokens (?) in the SQL query.</param>
299 + /// <returns>All results combined into an array</returns>
300 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
301 + /// <exception cref="InstallerException">the View could not be executed</exception>
302 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
303 + /// <remarks><p>
304 + /// Multiple rows columns will be collapsed into a single on-dimensional list.
305 + /// </p><p>
306 + /// Win32 MSI APIs:
307 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
308 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
309 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
310 + /// </p></remarks>
311 + public IList<string> ExecuteStringQuery(string sql, Record record)
312 + {
313 + if (String.IsNullOrEmpty(sql))
314 + {
315 + throw new ArgumentNullException("sql");
316 + }
317 +
318 + using (View view = this.OpenView(sql))
319 + {
320 + view.Execute(record);
321 + IList<string> results = new List<string>();
322 + int fieldCount = 0;
323 +
324 + foreach (Record rec in view) using (rec)
325 + {
326 + if (fieldCount == 0) fieldCount = rec.FieldCount;
327 + for (int i = 1; i <= fieldCount; i++)
328 + {
329 + results.Add(rec.GetString(i));
330 + }
331 + }
332 +
333 + return results;
334 + }
335 + }
336 +
337 + /// <summary>
338 + /// Executes the specified SQL SELECT query and returns a single result.
339 + /// </summary>
340 + /// <param name="sqlFormat">SQL query string, which may contain format items</param>
341 + /// <param name="args">Zero or more objects to format</param>
342 + /// <returns>First field of the first result</returns>
343 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
344 + /// <exception cref="InstallerException">the View could not be executed
345 + /// or the query returned 0 results</exception>
346 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
347 + /// <remarks><p>
348 + /// The <paramref name="sqlFormat"/> parameter is formatted using
349 + /// <see cref="String.Format(string,object[])"/>.
350 + /// </p><p>
351 + /// Win32 MSI APIs:
352 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
353 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
354 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
355 + /// </p></remarks>
356 + public object ExecuteScalar(string sqlFormat, params object[] args)
357 + {
358 + if (String.IsNullOrEmpty(sqlFormat))
359 + {
360 + throw new ArgumentNullException("sqlFormat");
361 + }
362 +
363 + return this.ExecuteScalar(
364 + args == null || args.Length == 0 ?
365 + sqlFormat : String.Format(CultureInfo.InvariantCulture, sqlFormat, args),
366 + (Record) null);
367 + }
368 +
369 + /// <summary>
370 + /// Executes the specified SQL SELECT query and returns a single result.
371 + /// </summary>
372 + /// <param name="sql">SQL SELECT query string</param>
373 + /// <param name="record">Optional Record object containing the values that replace
374 + /// the parameter tokens (?) in the SQL query.</param>
375 + /// <returns>First field of the first result</returns>
376 + /// <exception cref="BadQuerySyntaxException">the SQL syntax is invalid</exception>
377 + /// <exception cref="InstallerException">the View could not be executed
378 + /// or the query returned 0 results</exception>
379 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
380 + /// <remarks><p>
381 + /// Win32 MSI APIs:
382 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseopenview.asp">MsiDatabaseOpenView</a>,
383 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>,
384 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
385 + /// </p></remarks>
386 + public object ExecuteScalar(string sql, Record record)
387 + {
388 + if (String.IsNullOrEmpty(sql))
389 + {
390 + throw new ArgumentNullException("sql");
391 + }
392 +
393 + View view = this.OpenView(sql);
394 + Record rec = null;
395 + try
396 + {
397 + view.Execute(record);
398 + rec = view.Fetch();
399 + if (rec == null)
400 + {
401 + throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.NO_MORE_ITEMS);
402 + }
403 + return rec[1];
404 + }
405 + finally
406 + {
407 + if (rec != null) rec.Close();
408 + view.Close();
409 + }
410 + }
411 + }
412 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/DatabaseTransform.cs new
+278
@@ -0,0 +1,278 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Globalization;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + public partial class Database
11 + {
12 + /// <summary>
13 + /// Creates a transform that, when applied to the object database, results in the reference database.
14 + /// </summary>
15 + /// <param name="referenceDatabase">Database that does not include the changes</param>
16 + /// <param name="transformFile">Name of the generated transform file, or null to only
17 + /// check whether or not the two database are identical</param>
18 + /// <returns>true if a transform is generated, or false if a transform is not generated
19 + /// because there are no differences between the two databases.</returns>
20 + /// <exception cref="InstallerException">the transform could not be generated</exception>
21 + /// <exception cref="InvalidHandleException">a Database handle is invalid</exception>
22 + /// <remarks><p>
23 + /// A transform can add non-primary key columns to the end of a table. A transform cannot
24 + /// be created that adds primary key columns to a table. A transform cannot be created that
25 + /// changes the order, names, or definitions of columns.
26 + /// </p><p>
27 + /// If the transform is to be applied during an installation you must use the
28 + /// <see cref="Database.CreateTransformSummaryInfo"/> method to populate the
29 + /// summary information stream.
30 + /// </p><p>
31 + /// Win32 MSI API:
32 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabasegeneratetransform.asp">MsiDatabaseGenerateTransform</a>
33 + /// </p></remarks>
34 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
35 + public bool GenerateTransform(Database referenceDatabase, string transformFile)
36 + {
37 + if (referenceDatabase == null)
38 + {
39 + throw new ArgumentNullException("referenceDatabase");
40 + }
41 +
42 + if (String.IsNullOrEmpty(transformFile))
43 + {
44 + throw new ArgumentNullException("transformFile");
45 + }
46 +
47 + uint ret = NativeMethods.MsiDatabaseGenerateTransform((int) this.Handle, (int) referenceDatabase.Handle, transformFile, 0, 0);
48 + if (ret == (uint) NativeMethods.Error.NO_DATA)
49 + {
50 + return false;
51 + }
52 + else if (ret != 0)
53 + {
54 + throw InstallerException.ExceptionFromReturnCode(ret);
55 + }
56 + return true;
57 + }
58 +
59 + /// <summary>
60 + /// Creates and populates the summary information stream of an existing transform file, and
61 + /// fills in the properties with the base and reference ProductCode and ProductVersion.
62 + /// </summary>
63 + /// <param name="referenceDatabase">Database that does not include the changes</param>
64 + /// <param name="transformFile">Name of the generated transform file</param>
65 + /// <param name="errors">Error conditions that should be suppressed
66 + /// when the transform is applied</param>
67 + /// <param name="validations">Defines which properties should be validated
68 + /// to verify that this transform can be applied to a database.</param>
69 + /// <exception cref="InstallerException">the transform summary info could not be
70 + /// generated</exception>
71 + /// <exception cref="InvalidHandleException">a Database handle is invalid</exception>
72 + /// <remarks><p>
73 + /// Win32 MSI API:
74 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreatetransformsummaryinfo.asp">MsiCreateTransformSummaryInfo</a>
75 + /// </p></remarks>
76 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
77 + public void CreateTransformSummaryInfo(
78 + Database referenceDatabase,
79 + string transformFile,
80 + TransformErrors errors,
81 + TransformValidations validations)
82 + {
83 + if (referenceDatabase == null)
84 + {
85 + throw new ArgumentNullException("referenceDatabase");
86 + }
87 +
88 + if (String.IsNullOrEmpty(transformFile))
89 + {
90 + throw new ArgumentNullException("transformFile");
91 + }
92 +
93 + uint ret = NativeMethods.MsiCreateTransformSummaryInfo(
94 + (int) this.Handle,
95 + (int) referenceDatabase.Handle,
96 + transformFile,
97 + (int) errors,
98 + (int) validations);
99 + if (ret != 0)
100 + {
101 + throw InstallerException.ExceptionFromReturnCode(ret);
102 + }
103 + }
104 +
105 + /// <summary>
106 + /// Apply a transform to the database, recording the changes in the "_TransformView" table.
107 + /// </summary>
108 + /// <param name="transformFile">Path to the transform file</param>
109 + /// <exception cref="InstallerException">the transform could not be applied</exception>
110 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
111 + /// <remarks><p>
112 + /// Win32 MSI API:
113 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
114 + /// </p></remarks>
115 + public void ViewTransform(string transformFile)
116 + {
117 + TransformErrors transformErrors =
118 + TransformErrors.AddExistingRow |
119 + TransformErrors.DelMissingRow |
120 + TransformErrors.AddExistingTable |
121 + TransformErrors.DelMissingTable |
122 + TransformErrors.UpdateMissingRow |
123 + TransformErrors.ChangeCodePage |
124 + TransformErrors.ViewTransform;
125 + this.ApplyTransform(transformFile, transformErrors);
126 + }
127 +
128 + /// <summary>
129 + /// Apply a transform to the database, suppressing any error conditions
130 + /// specified by the transform's summary information.
131 + /// </summary>
132 + /// <param name="transformFile">Path to the transform file</param>
133 + /// <exception cref="InstallerException">the transform could not be applied</exception>
134 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
135 + /// <remarks><p>
136 + /// Win32 MSI API:
137 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
138 + /// </p></remarks>
139 + public void ApplyTransform(string transformFile)
140 + {
141 + if (String.IsNullOrEmpty(transformFile))
142 + {
143 + throw new ArgumentNullException("transformFile");
144 + }
145 +
146 + TransformErrors errorConditionsToSuppress;
147 + using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false))
148 + {
149 + int errorConditions = transformSummInfo.CharacterCount & 0xFFFF;
150 + errorConditionsToSuppress = (TransformErrors) errorConditions;
151 + }
152 + this.ApplyTransform(transformFile, errorConditionsToSuppress);
153 + }
154 +
155 + /// <summary>
156 + /// Apply a transform to the database, specifying error conditions to suppress.
157 + /// </summary>
158 + /// <param name="transformFile">Path to the transform file</param>
159 + /// <param name="errorConditionsToSuppress">Error conditions that are to be suppressed</param>
160 + /// <exception cref="InstallerException">the transform could not be applied</exception>
161 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
162 + /// <remarks><p>
163 + /// Win32 MSI API:
164 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidatabaseapplytransform.asp">MsiDatabaseApplyTransform</a>
165 + /// </p></remarks>
166 + public void ApplyTransform(string transformFile, TransformErrors errorConditionsToSuppress)
167 + {
168 + if (String.IsNullOrEmpty(transformFile))
169 + {
170 + throw new ArgumentNullException("transformFile");
171 + }
172 +
173 + uint ret = NativeMethods.MsiDatabaseApplyTransform((int) this.Handle, transformFile, (int) errorConditionsToSuppress);
174 + if (ret != 0)
175 + {
176 + throw InstallerException.ExceptionFromReturnCode(ret);
177 + }
178 + }
179 +
180 + /// <summary>
181 + /// Checks whether a transform is valid for this Database, according to its validation data and flags.
182 + /// </summary>
183 + /// <param name="transformFile">Path to the transform file</param>
184 + /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns>
185 + /// <exception cref="InstallerException">the transform could not be applied</exception>
186 + /// <exception cref="InvalidHandleException">the Database handle is invalid</exception>
187 + public bool IsTransformValid(string transformFile)
188 + {
189 + if (String.IsNullOrEmpty(transformFile))
190 + {
191 + throw new ArgumentNullException("transformFile");
192 + }
193 +
194 + using (SummaryInfo transformSummInfo = new SummaryInfo(transformFile, false))
195 + {
196 + return this.IsTransformValid(transformSummInfo);
197 + }
198 + }
199 +
200 + /// <summary>
201 + /// Checks whether a transform is valid for this Database, according to its SummaryInfo data.
202 + /// </summary>
203 + /// <param name="transformSummaryInfo">SummaryInfo data of a transform file</param>
204 + /// <returns>true if the transform can be validly applied to this Database; false otherwise</returns>
205 + /// <exception cref="InstallerException">error processing summary info</exception>
206 + /// <exception cref="InvalidHandleException">the Database or SummaryInfo handle is invalid</exception>
207 + public bool IsTransformValid(SummaryInfo transformSummaryInfo)
208 + {
209 + if (transformSummaryInfo == null)
210 + {
211 + throw new ArgumentNullException("transformSummaryInfo");
212 + }
213 +
214 + string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3);
215 + string targetProductCode = rev[0].Substring(0, 38);
216 + string targetProductVersion = rev[0].Substring(38);
217 + string upgradeCode = rev[2];
218 +
219 + string[] templ = transformSummaryInfo.Template.Split(new char[] { ';' }, 2);
220 + int targetProductLanguage = 0;
221 + if (templ.Length >= 2 && templ[1].Length > 0)
222 + {
223 + targetProductLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat);
224 + }
225 +
226 + int flags = transformSummaryInfo.CharacterCount;
227 + int validateFlags = flags >> 16;
228 +
229 + string thisProductCode = this.ExecutePropertyQuery("ProductCode");
230 + string thisProductVersion = this.ExecutePropertyQuery("ProductVersion");
231 + string thisUpgradeCode = this.ExecutePropertyQuery("UpgradeCode");
232 + string thisProductLang = this.ExecutePropertyQuery("ProductLanguage");
233 + int thisProductLanguage = 0;
234 + if (!String.IsNullOrEmpty(thisProductLang))
235 + {
236 + thisProductLanguage = Int32.Parse(thisProductLang, CultureInfo.InvariantCulture.NumberFormat);
237 + }
238 +
239 + if ((validateFlags & (int) TransformValidations.Product) != 0 &&
240 + thisProductCode != targetProductCode)
241 + {
242 + return false;
243 + }
244 +
245 + if ((validateFlags & (int) TransformValidations.UpgradeCode) != 0 &&
246 + thisUpgradeCode != upgradeCode)
247 + {
248 + return false;
249 + }
250 +
251 + if ((validateFlags & (int) TransformValidations.Language) != 0 &&
252 + targetProductLanguage != 0 && thisProductLanguage != targetProductLanguage)
253 + {
254 + return false;
255 + }
256 +
257 + Version thisProductVer = new Version(thisProductVersion);
258 + Version targetProductVer = new Version(targetProductVersion);
259 + if ((validateFlags & (int) TransformValidations.UpdateVersion) != 0)
260 + {
261 + if (thisProductVer.Major != targetProductVer.Major) return false;
262 + if (thisProductVer.Minor != targetProductVer.Minor) return false;
263 + if (thisProductVer.Build != targetProductVer.Build) return false;
264 + }
265 + else if ((validateFlags & (int) TransformValidations.MinorVersion) != 0)
266 + {
267 + if (thisProductVer.Major != targetProductVer.Major) return false;
268 + if (thisProductVer.Minor != targetProductVer.Minor) return false;
269 + }
270 + else if ((validateFlags & (int) TransformValidations.MajorVersion) != 0)
271 + {
272 + if (thisProductVer.Major != targetProductVer.Major) return false;
273 + }
274 +
275 + return true;
276 + }
277 + }
278 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/EmbeddedUIProxy.cs new
+231
@@ -0,0 +1,231 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections;
7 + using System.Configuration;
8 + using System.Diagnostics.CodeAnalysis;
9 + using System.IO;
10 + using System.Reflection;
11 + using System.Runtime.InteropServices;
12 + using System.Security;
13 + using System.Text;
14 +
15 + /// <summary>
16 + /// Managed-code portion of the embedded UI proxy.
17 + /// </summary>
18 + internal static class EmbeddedUIProxy
19 + {
20 + private static IEmbeddedUI uiInstance;
21 + private static string uiClass;
22 +
23 + private static bool DebugBreakEnabled(string method)
24 + {
25 + return CustomActionProxy.DebugBreakEnabled(new string[] { method, EmbeddedUIProxy.uiClass + "." + method } );
26 + }
27 +
28 + /// <summary>
29 + /// Initializes managed embedded UI by loading the UI class and invoking its Initialize method.
30 + /// </summary>
31 + /// <param name="sessionHandle">Integer handle to the installer session.</param>
32 + /// <param name="uiClass">Name of the class that implements the embedded UI. This must
33 + /// be of the form: &quot;AssemblyName!Namespace.Class&quot;</param>
34 + /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
35 + /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
36 + /// <returns>0 if the embedded UI was successfully loaded and initialized,
37 + /// ERROR_INSTALL_USEREXIT if the user canceled the installation during initialization,
38 + /// or ERROR_INSTALL_FAILURE if the embedded UI could not be initialized.</returns>
39 + /// <remarks>
40 + /// Due to interop limitations, the successful resulting UILevel is actually returned
41 + /// as the high-word of the return value instead of via a ref parameter.
42 + /// </remarks>
43 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
44 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
45 + public static int Initialize(int sessionHandle, string uiClass, int internalUILevel)
46 + {
47 + Session session = null;
48 +
49 + try
50 + {
51 + session = new Session((IntPtr) sessionHandle, false);
52 +
53 + if (String.IsNullOrEmpty(uiClass))
54 + {
55 + throw new ArgumentNullException("uiClass");
56 + }
57 +
58 + EmbeddedUIProxy.uiInstance = EmbeddedUIProxy.InstantiateUI(session, uiClass);
59 + }
60 + catch (Exception ex)
61 + {
62 + if (session != null)
63 + {
64 + try
65 + {
66 + session.Log("Exception while loading embedded UI:");
67 + session.Log(ex.ToString());
68 + }
69 + catch (Exception)
70 + {
71 + }
72 + }
73 + }
74 +
75 + if (EmbeddedUIProxy.uiInstance == null)
76 + {
77 + return (int) ActionResult.Failure;
78 + }
79 +
80 + try
81 + {
82 + string resourcePath = Path.GetDirectoryName(EmbeddedUIProxy.uiInstance.GetType().Assembly.Location);
83 + InstallUIOptions uiOptions = (InstallUIOptions) internalUILevel;
84 + if (EmbeddedUIProxy.DebugBreakEnabled("Initialize"))
85 + {
86 + System.Diagnostics.Debugger.Launch();
87 + }
88 +
89 + if (EmbeddedUIProxy.uiInstance.Initialize(session, resourcePath, ref uiOptions))
90 + {
91 + // The embedded UI initialized and the installation should continue
92 + // with internal UI reset according to options.
93 + return ((int) uiOptions) << 16;
94 + }
95 + else
96 + {
97 + // The embedded UI did not initialize but the installation should still continue
98 + // with internal UI reset according to options.
99 + return (int) uiOptions;
100 + }
101 + }
102 + catch (InstallCanceledException)
103 + {
104 + // The installation was canceled by the user.
105 + return (int) ActionResult.UserExit;
106 + }
107 + catch (Exception ex)
108 + {
109 + // An unhandled exception causes the installation to fail immediately.
110 + session.Log("Exception thrown by embedded UI initialization:");
111 + session.Log(ex.ToString());
112 + return (int) ActionResult.Failure;
113 + }
114 + }
115 +
116 + /// <summary>
117 + /// Passes a progress message to the UI class.
118 + /// </summary>
119 + /// <param name="messageType">Installer message type and message box options.</param>
120 + /// <param name="recordHandle">Handle to a record containing message data.</param>
121 + /// <returns>Return value returned by the UI class.</returns>
122 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
123 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
124 + public static int ProcessMessage(int messageType, int recordHandle)
125 + {
126 + if (EmbeddedUIProxy.uiInstance != null)
127 + {
128 + try
129 + {
130 + int msgType = messageType & 0x7F000000;
131 + int buttons = messageType & 0x0000000F;
132 + int icon = messageType & 0x000000F0;
133 + int defButton = messageType & 0x00000F00;
134 +
135 + Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null);
136 + using (msgRec)
137 + {
138 + if (EmbeddedUIProxy.DebugBreakEnabled("ProcessMessage"))
139 + {
140 + System.Diagnostics.Debugger.Launch();
141 + }
142 +
143 + return (int) EmbeddedUIProxy.uiInstance.ProcessMessage(
144 + (InstallMessage) msgType,
145 + msgRec,
146 + (MessageButtons) buttons,
147 + (MessageIcon) icon,
148 + (MessageDefaultButton) defButton);
149 + }
150 + }
151 + catch (Exception)
152 + {
153 + // Ignore it... just hope future messages will not throw exceptions.
154 + }
155 + }
156 +
157 + return 0;
158 + }
159 +
160 + /// <summary>
161 + /// Passes a shutdown message to the UI class.
162 + /// </summary>
163 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
164 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
165 + public static void Shutdown()
166 + {
167 + if (EmbeddedUIProxy.uiInstance != null)
168 + {
169 + try
170 + {
171 + if (EmbeddedUIProxy.DebugBreakEnabled("Shutdown"))
172 + {
173 + System.Diagnostics.Debugger.Launch();
174 + }
175 +
176 + EmbeddedUIProxy.uiInstance.Shutdown();
177 + }
178 + catch (Exception)
179 + {
180 + // Nothing to do at this point... the installation is done anyway.
181 + }
182 +
183 + EmbeddedUIProxy.uiInstance = null;
184 + }
185 + }
186 +
187 + /// <summary>
188 + /// Instantiates a UI class from a given assembly and class name.
189 + /// </summary>
190 + /// <param name="session">Installer session, for logging.</param>
191 + /// <param name="uiClass">Name of the class that implements the embedded UI. This must
192 + /// be of the form: &quot;AssemblyName!Namespace.Class&quot;</param>
193 + /// <returns>Interface on the UI class for handling UI messages.</returns>
194 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
195 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
196 + private static IEmbeddedUI InstantiateUI(Session session, string uiClass)
197 + {
198 + int assemblySplit = uiClass.IndexOf('!');
199 + if (assemblySplit < 0)
200 + {
201 + session.Log("Error: invalid embedded UI assembly and class:" + uiClass);
202 + return null;
203 + }
204 +
205 + string assemblyName = uiClass.Substring(0, assemblySplit);
206 + EmbeddedUIProxy.uiClass = uiClass.Substring(assemblySplit + 1);
207 +
208 + Assembly uiAssembly;
209 + try
210 + {
211 + uiAssembly = AppDomain.CurrentDomain.Load(assemblyName);
212 +
213 + // This calls out to CustomActionProxy.DebugBreakEnabled() directly instead
214 + // of calling EmbeddedUIProxy.DebugBreakEnabled() because we don't compose a
215 + // class.method name for this breakpoint.
216 + if (CustomActionProxy.DebugBreakEnabled(new string[] { "EmbeddedUI" }))
217 + {
218 + System.Diagnostics.Debugger.Launch();
219 + }
220 +
221 + return (IEmbeddedUI) uiAssembly.CreateInstance(EmbeddedUIProxy.uiClass);
222 + }
223 + catch (Exception ex)
224 + {
225 + session.Log("Error: could not load embedded UI class " + EmbeddedUIProxy.uiClass + " from assembly: " + assemblyName);
226 + session.Log(ex.ToString());
227 + return null;
228 + }
229 + }
230 + }
231 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Enums.cs new
+909
@@ -0,0 +1,909 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Diagnostics.CodeAnalysis;
7 +
8 + // Enumerations are in alphabetical order.
9 +
10 + /// <summary>
11 + /// Specifies a return status value for custom actions.
12 + /// </summary>
13 + public enum ActionResult : int
14 + {
15 + /// <summary>Action completed successfully.</summary>
16 + Success = 0,
17 +
18 + /// <summary>Skip remaining actions, not an error.</summary>
19 + SkipRemainingActions = 259,
20 +
21 + /// <summary>User terminated prematurely.</summary>
22 + UserExit = 1602,
23 +
24 + /// <summary>Unrecoverable error or unhandled exception occurred.</summary>
25 + Failure = 1603,
26 +
27 + /// <summary>Action not executed.</summary>
28 + NotExecuted = 1626,
29 + }
30 +
31 + /// <summary>
32 + /// Specifies the open mode for a <see cref="Database"/>.
33 + /// </summary>
34 + public enum DatabaseOpenMode : int
35 + {
36 + /// <summary>Open a database read-only, no persistent changes.</summary>
37 + ReadOnly = 0,
38 +
39 + /// <summary>Open a database read/write in transaction mode.</summary>
40 + Transact = 1,
41 +
42 + /// <summary>Open a database direct read/write without transaction.</summary>
43 + Direct = 2,
44 +
45 + /// <summary>Create a new database, transact mode read/write.</summary>
46 + Create = 3,
47 +
48 + /// <summary>Create a new database, direct mode read/write.</summary>
49 + CreateDirect = 4,
50 + }
51 +
52 + /// <summary>
53 + /// Log modes available for <see cref="Installer.EnableLog(InstallLogModes,string)"/>
54 + /// and <see cref="Installer.SetExternalUI(ExternalUIHandler,InstallLogModes)"/>.
55 + /// </summary>
56 + [Flags]
57 + public enum InstallLogModes : int
58 + {
59 + /// <summary>Disable logging.</summary>
60 + None = 0,
61 +
62 + /// <summary>Log out of memory or fatal exit information.</summary>
63 + FatalExit = (1 << ((int) InstallMessage.FatalExit >> 24)),
64 +
65 + /// <summary>Log error messages.</summary>
66 + Error = (1 << ((int) InstallMessage.Error >> 24)),
67 +
68 + /// <summary>Log warning messages.</summary>
69 + Warning = (1 << ((int) InstallMessage.Warning >> 24)),
70 +
71 + /// <summary>Log user requests.</summary>
72 + User = (1 << ((int) InstallMessage.User >> 24)),
73 +
74 + /// <summary>Log status messages that are not displayed.</summary>
75 + Info = (1 << ((int) InstallMessage.Info >> 24)),
76 +
77 + /// <summary>Log request to determine a valid source location.</summary>
78 + ResolveSource = (1 << ((int) InstallMessage.ResolveSource >> 24)),
79 +
80 + /// <summary>Log insufficient disk space error.</summary>
81 + OutOfDiskSpace = (1 << ((int) InstallMessage.OutOfDiskSpace >> 24)),
82 +
83 + /// <summary>Log the start of installation actions.</summary>
84 + ActionStart = (1 << ((int) InstallMessage.ActionStart >> 24)),
85 +
86 + /// <summary>Log the data record for installation actions.</summary>
87 + ActionData = (1 << ((int) InstallMessage.ActionData >> 24)),
88 +
89 + /// <summary>Log parameters for user-interface initialization.</summary>
90 + CommonData = (1 << ((int) InstallMessage.CommonData >> 24)),
91 +
92 + /// <summary>Log the property values at termination.</summary>
93 + PropertyDump = (1 << ((int) InstallMessage.Progress >> 24)), // log only
94 +
95 + /// <summary>
96 + /// Sends large amounts of information to log file not generally useful to users.
97 + /// May be used for support.
98 + /// </summary>
99 + Verbose = (1 << ((int) InstallMessage.Initialize >> 24)), // log only
100 +
101 + /// <summary>
102 + /// Log extra debugging information.
103 + /// </summary>
104 + ExtraDebug = (1 << ((int) InstallMessage.Terminate >> 24)), // log only
105 +
106 + /// <summary>
107 + /// Log only on error.
108 + /// </summary>
109 + LogOnlyOnError = (1 << ((int) InstallMessage.ShowDialog >> 24)), // log only
110 +
111 + /// <summary>
112 + /// Log progress bar information. This message includes information on units so far and total number
113 + /// of units. See <see cref="Session.Message"/> for an explanation of the message format. This message
114 + /// is only sent to an external user interface and is not logged.
115 + /// </summary>
116 + Progress = (1 << ((int) InstallMessage.Progress >> 24)), // external handler only
117 +
118 + /// <summary>
119 + /// If this is not a quiet installation, then the basic UI has been initialized. If this is a full
120 + /// UI installation, the Full UI is not yet initialized. This message is only sent to an external
121 + /// user interface and is not logged.
122 + /// </summary>
123 + Initialize = (1 << ((int) InstallMessage.Initialize >> 24)), // external handler only
124 +
125 + /// <summary>
126 + /// If a full UI is being used, the full UI has ended. If this is not a quiet installation, the basic
127 + /// UI has not yet ended. This message is only sent to an external user interface and is not logged.
128 + /// </summary>
129 + Terminate = (1 << ((int) InstallMessage.Terminate >> 24)), // external handler only
130 +
131 + /// <summary>
132 + /// Sent prior to display of the Full UI dialog. This message is only sent to an external user
133 + /// interface and is not logged.
134 + /// </summary>
135 + ShowDialog = (1 << ((int) InstallMessage.ShowDialog >> 24)), // external handler only
136 +
137 + /// <summary>
138 + /// List of files in use that need to be replaced.
139 + /// </summary>
140 + FilesInUse = (1 << ((int) InstallMessage.FilesInUse >> 24)), // external handler only
141 +
142 + /// <summary>
143 + /// [MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart.
144 + /// </summary>
145 + RMFilesInUse = (1 << ((int) InstallMessage.RMFilesInUse >> 24)), // external handler only
146 + }
147 +
148 + /// <summary>
149 + /// Type of message to be processed by <see cref="Session.Message"/>,
150 + /// <see cref="ExternalUIHandler"/>, or <see cref="ExternalUIRecordHandler"/>.
151 + /// </summary>
152 + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
153 + public enum InstallMessage : int
154 + {
155 + /// <summary>Premature termination, possibly fatal OOM.</summary>
156 + FatalExit = 0x00000000,
157 +
158 + /// <summary>Formatted error message.</summary>
159 + Error = 0x01000000,
160 +
161 + /// <summary>Formatted warning message.</summary>
162 + Warning = 0x02000000,
163 +
164 + /// <summary>User request message.</summary>
165 + User = 0x03000000,
166 +
167 + /// <summary>Informative message for log.</summary>
168 + Info = 0x04000000,
169 +
170 + /// <summary>List of files in use that need to be replaced.</summary>
171 + FilesInUse = 0x05000000,
172 +
173 + /// <summary>Request to determine a valid source location.</summary>
174 + ResolveSource = 0x06000000,
175 +
176 + /// <summary>Insufficient disk space message.</summary>
177 + OutOfDiskSpace = 0x07000000,
178 +
179 + /// <summary>Start of action: action name &amp; description.</summary>
180 + ActionStart = 0x08000000,
181 +
182 + /// <summary>Formatted data associated with individual action item.</summary>
183 + ActionData = 0x09000000,
184 +
185 + /// <summary>Progress gauge info: units so far, total.</summary>
186 + Progress = 0x0A000000,
187 +
188 + /// <summary>Product info for dialog: language Id, dialog caption.</summary>
189 + CommonData = 0x0B000000,
190 +
191 + /// <summary>Sent prior to UI initialization, no string data.</summary>
192 + Initialize = 0x0C000000,
193 +
194 + /// <summary>Sent after UI termination, no string data.</summary>
195 + Terminate = 0x0D000000,
196 +
197 + /// <summary>Sent prior to display or authored dialog or wizard.</summary>
198 + ShowDialog = 0x0E000000,
199 +
200 + /// <summary>[MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart.</summary>
201 + RMFilesInUse = 0x19000000,
202 +
203 + /// <summary>[MSI 4.5] Sent prior to install of a product.</summary>
204 + InstallStart = 0x1A000000,
205 +
206 + /// <summary>[MSI 4.5] Sent after install of a product.</summary>
207 + InstallEnd = 0x1B000000,
208 + }
209 +
210 + /// <summary>
211 + /// Specifies the install mode for <see cref="Installer.ProvideComponent"/> or <see cref="Installer.ProvideQualifiedComponent"/>.
212 + /// </summary>
213 + public enum InstallMode : int
214 + {
215 + /// <summary>Provide the component only if the feature's installation state is <see cref="InstallState.Local"/>.</summary>
216 + NoSourceResolution = -3,
217 +
218 + /// <summary>Only check that the component is registered, without verifying that the key file of the component exists.</summary>
219 + NoDetection = -2,
220 +
221 + /// <summary>Provide the component only if the feature exists.</summary>
222 + Existing = -1,
223 +
224 + /// <summary>Provide the component and perform any installation necessary to provide the component.</summary>
225 + Default = 0,
226 + }
227 +
228 + /// <summary>
229 + /// Specifies the run mode for <see cref="Session.GetMode"/>.
230 + /// </summary>
231 + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
232 + public enum InstallRunMode : int
233 + {
234 + /// <summary>The administrative mode is installing, or the product is installing.</summary>
235 + Admin = 0,
236 +
237 + /// <summary>The advertisements are installing or the product is installing or updating.</summary>
238 + Advertise = 1,
239 +
240 + /// <summary>An existing installation is being modified or there is a new installation.</summary>
241 + Maintenance = 2,
242 +
243 + /// <summary>Rollback is enabled.</summary>
244 + RollbackEnabled = 3,
245 +
246 + /// <summary>The log file is active. It was enabled prior to the installation session.</summary>
247 + LogEnabled = 4,
248 +
249 + /// <summary>Execute operations are spooling or they are in the determination phase.</summary>
250 + Operations = 5,
251 +
252 + /// <summary>A reboot is necessary after a successful installation (settable).</summary>
253 + RebootAtEnd = 6,
254 +
255 + /// <summary>A reboot is necessary to continue the installation (settable).</summary>
256 + RebootNow = 7,
257 +
258 + /// <summary>Files from cabinets and Media table files are installing.</summary>
259 + Cabinet = 8,
260 +
261 + /// <summary>The source LongFileNames is suppressed through the PID_MSISOURCE summary property.</summary>
262 + SourceShortNames = 9,
263 +
264 + /// <summary>The target LongFileNames is suppressed through the SHORTFILENAMES property.</summary>
265 + TargetShortNames = 10,
266 +
267 + // <summary>Reserved for future use.</summary>
268 + //Reserved11 = 11,
269 +
270 + /// <summary>The operating system is Windows 95, Windows 98, or Windows ME.</summary>
271 + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x")]
272 + Windows9x = 12,
273 +
274 + /// <summary>The operating system supports demand installation.</summary>
275 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zaw")]
276 + ZawEnabled = 13,
277 +
278 + // <summary>Reserved for future use.</summary>
279 + //Reserved14 = 14,
280 +
281 + // <summary>Reserved for future use.</summary>
282 + //Reserved15 = 15,
283 +
284 + /// <summary>A custom action called from install script execution.</summary>
285 + Scheduled = 16,
286 +
287 + /// <summary>A custom action called from rollback execution script.</summary>
288 + Rollback = 17,
289 +
290 + /// <summary>A custom action called from commit execution script.</summary>
291 + Commit = 18,
292 + }
293 +
294 + /// <summary>
295 + /// Installed state of a Component or Feature.
296 + /// </summary>
297 + public enum InstallState : int
298 + {
299 + /// <summary>The component is disabled.</summary>
300 + NotUsed = -7,
301 +
302 + /// <summary>The installation configuration data is corrupt.</summary>
303 + BadConfig = -6,
304 +
305 + /// <summary>The installation is suspended or in progress.</summary>
306 + Incomplete = -5,
307 +
308 + /// <summary>Component is set to run from source, but source is unavailable.</summary>
309 + SourceAbsent = -4,
310 +
311 + /// <summary>The buffer overflow is returned.</summary>
312 + MoreData = -3,
313 +
314 + /// <summary>An invalid parameter was passed to the function.</summary>
315 + InvalidArgument = -2,
316 +
317 + /// <summary>An unrecognized product or feature name was passed to the function.</summary>
318 + Unknown = -1,
319 +
320 + /// <summary>The component is broken.</summary>
321 + Broken = 0,
322 +
323 + /// <summary>The feature is advertised.</summary>
324 + Advertised = 1,
325 +
326 + /// <summary>The component is being removed. In action state and not settable.</summary>
327 + Removed = 1,
328 +
329 + /// <summary>The component is not installed, or action state is absent but clients remain.</summary>
330 + Absent = 2,
331 +
332 + /// <summary>The component is installed on the local drive.</summary>
333 + Local = 3,
334 +
335 + /// <summary>The component will run from the source, CD, or network.</summary>
336 + Source = 4,
337 +
338 + /// <summary>The component will be installed in the default location: local or source.</summary>
339 + Default = 5,
340 + }
341 +
342 + /// <summary>
343 + /// Specifies the type of installation for <see cref="Installer.ApplyPatch(string,string,InstallType,string)"/>.
344 + /// </summary>
345 + public enum InstallType : int
346 + {
347 + /// <summary>Searches system for products to patch.</summary>
348 + Default = 0,
349 +
350 + /// <summary>Indicates a administrative installation.</summary>
351 + NetworkImage = 1,
352 +
353 + /// <summary>Indicates a particular instance.</summary>
354 + SingleInstance = 2,
355 + }
356 +
357 + /// <summary>
358 + /// Level of the installation user interface, specified with
359 + /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/>.
360 + /// </summary>
361 + [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
362 + [Flags]
363 + public enum InstallUIOptions : int
364 + {
365 + /// <summary>Does not change UI level.</summary>
366 + NoChange = 0,
367 +
368 + /// <summary>Uses Default UI level.</summary>
369 + Default = 1,
370 +
371 + /// <summary>Silent installation.</summary>
372 + Silent = 2,
373 +
374 + /// <summary>Simple progress and error handling.</summary>
375 + Basic = 3,
376 +
377 + /// <summary>Authored UI, wizard dialogs suppressed.</summary>
378 + Reduced = 4,
379 +
380 + /// <summary>Authored UI with wizards, progress, and errors.</summary>
381 + Full = 5,
382 +
383 + /// <summary>
384 + /// When combined with the <see cref="Basic"/> value, the installer does not display
385 + /// the cancel button in the progress dialog.
386 + /// </summary>
387 + HideCancel = 0x20,
388 +
389 + /// <summary>
390 + /// When combined with the <see cref="Basic"/> value, the installer displays progress
391 + /// dialog boxes but does not display any modal dialog boxes or error dialog boxes.
392 + /// </summary>
393 + ProgressOnly = 0x40,
394 +
395 + /// <summary>
396 + /// When combined with another value, the installer displays a modal dialog
397 + /// box at the end of a successful installation or if there has been an error.
398 + /// No dialog box is displayed if the user cancels.
399 + /// </summary>
400 + EndDialog = 0x80,
401 +
402 + /// <summary>
403 + /// Forces display of the source resolution dialog even if the UI is otherwise silent.
404 + /// </summary>
405 + SourceResolutionOnly = 0x100,
406 +
407 + /// <summary>
408 + /// [MSI 5.0] Forces display of the UAC dialog even if the UI is otherwise silent.
409 + /// </summary>
410 + UacOnly = 0x200,
411 + }
412 +
413 + /// <summary>
414 + /// Specifies a return status value for message handlers. These values are returned by
415 + /// <see cref="Session.Message"/>, <see cref="ExternalUIHandler"/>, and <see cref="IEmbeddedUI.ProcessMessage"/>.
416 + /// </summary>
417 + public enum MessageResult : int
418 + {
419 + /// <summary>An error was found in the message handler.</summary>
420 + Error = -1,
421 +
422 + /// <summary>No action was taken.</summary>
423 + None = 0,
424 +
425 + /// <summary>IDOK</summary>
426 + [SuppressMessage("Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase")]
427 + OK = 1,
428 +
429 + /// <summary>IDCANCEL</summary>
430 + Cancel = 2,
431 +
432 + /// <summary>IDABORT</summary>
433 + Abort = 3,
434 +
435 + /// <summary>IDRETRY</summary>
436 + Retry = 4,
437 +
438 + /// <summary>IDIGNORE</summary>
439 + Ignore = 5,
440 +
441 + /// <summary>IDYES</summary>
442 + Yes = 6,
443 +
444 + /// <summary>IDNO</summary>
445 + No = 7,
446 + }
447 +
448 + /// <summary>
449 + /// Specifies constants defining which buttons to display for a message. This can be cast to
450 + /// the MessageBoxButtons enum in System.Windows.Forms and System.Windows.
451 + /// </summary>
452 + public enum MessageButtons
453 + {
454 + /// <summary>
455 + /// The message contains an OK button.
456 + /// </summary>
457 + OK = 0,
458 +
459 + /// <summary>
460 + /// The message contains OK and Cancel buttons.
461 + /// </summary>
462 + OKCancel = 1,
463 +
464 + /// <summary>
465 + /// The message contains Abort, Retry, and Ignore buttons.
466 + /// </summary>
467 + AbortRetryIgnore = 2,
468 +
469 + /// <summary>
470 + /// The message contains Yes, No, and Cancel buttons.
471 + /// </summary>
472 + YesNoCancel = 3,
473 +
474 + /// <summary>
475 + /// The message contains Yes and No buttons.
476 + /// </summary>
477 + YesNo = 4,
478 +
479 + /// <summary>
480 + /// The message contains Retry and Cancel buttons.
481 + /// </summary>
482 + RetryCancel = 5,
483 + }
484 +
485 + /// <summary>
486 + /// Specifies constants defining which information to display. This can be cast to
487 + /// the MessageBoxIcon enum in System.Windows.Forms and System.Windows.
488 + /// </summary>
489 + public enum MessageIcon
490 + {
491 + /// <summary>
492 + /// The message contain no symbols.
493 + /// </summary>
494 + None = 0,
495 +
496 + /// <summary>
497 + /// The message contains a symbol consisting of white X in a circle with a red background.
498 + /// </summary>
499 + Error = 16,
500 +
501 + /// <summary>
502 + /// The message contains a symbol consisting of a white X in a circle with a red background.
503 + /// </summary>
504 + Hand = 16,
505 +
506 + /// <summary>
507 + /// The message contains a symbol consisting of white X in a circle with a red background.
508 + /// </summary>
509 + Stop = 16,
510 +
511 + /// <summary>
512 + /// The message contains a symbol consisting of a question mark in a circle.
513 + /// </summary>
514 + Question = 32,
515 +
516 + /// <summary>
517 + /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background.
518 + /// </summary>
519 + Exclamation = 48,
520 +
521 + /// <summary>
522 + /// The message contains a symbol consisting of an exclamation point in a triangle with a yellow background.
523 + /// </summary>
524 + Warning = 48,
525 +
526 + /// <summary>
527 + /// The message contains a symbol consisting of a lowercase letter i in a circle.
528 + /// </summary>
529 + Information = 64,
530 +
531 + /// <summary>
532 + /// The message contains a symbol consisting of a lowercase letter i in a circle.
533 + /// </summary>
534 + Asterisk = 64,
535 + }
536 +
537 + /// <summary>
538 + /// Specifies constants defining the default button on a message. This can be cast to
539 + /// the MessageBoxDefaultButton enum in System.Windows.Forms and System.Windows.
540 + /// </summary>
541 + public enum MessageDefaultButton
542 + {
543 + /// <summary>
544 + /// The first button on the message is the default button.
545 + /// </summary>
546 + Button1 = 0,
547 +
548 + /// <summary>
549 + /// The second button on the message is the default button.
550 + /// </summary>
551 + Button2 = 256,
552 +
553 + /// <summary>
554 + /// The third button on the message is the default button.
555 + /// </summary>
556 + Button3 = 512,
557 + }
558 +
559 + /// <summary>
560 + /// Additional styles for use with message boxes.
561 + /// </summary>
562 + [Flags]
563 + internal enum MessageBoxStyles
564 + {
565 + /// <summary>
566 + /// The message box is created with the WS_EX_TOPMOST window style.
567 + /// </summary>
568 + TopMost = 0x00040000,
569 +
570 + /// <summary>
571 + /// The caller is a service notifying the user of an event.
572 + /// The function displays a message box on the current active desktop, even if there is no user logged on to the computer.
573 + /// </summary>
574 + ServiceNotification = 0x00200000,
575 + }
576 +
577 + /// <summary>
578 + /// Specifies the different patch states for <see cref="PatchInstallation.GetPatches(string, string, string, UserContexts, PatchStates)"/>.
579 + /// </summary>
580 + [Flags]
581 + public enum PatchStates : int
582 + {
583 + /// <summary>Invalid value.</summary>
584 + None = 0,
585 +
586 + /// <summary>Patches applied to a product.</summary>
587 + Applied = 1,
588 +
589 + /// <summary>Patches that are superseded by other patches.</summary>
590 + Superseded = 2,
591 +
592 + /// <summary>Patches that are obsolesced by other patches.</summary>
593 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")]
594 + Obsoleted = 4,
595 +
596 + /// <summary>Patches that are registered to a product but not applied.</summary>
597 + Registered = 8,
598 +
599 + /// <summary>All valid patch states.</summary>
600 + All = (Applied | Superseded | Obsoleted | Registered)
601 + }
602 +
603 + /// <summary>
604 + /// Specifies the reinstall mode for <see cref="Installer.ReinstallFeature"/> or <see cref="Installer.ReinstallProduct"/>.
605 + /// </summary>
606 + [Flags]
607 + public enum ReinstallModes : int
608 + {
609 + /// <summary>Reinstall only if file is missing.</summary>
610 + FileMissing = 0x00000002,
611 +
612 + /// <summary>Reinstall if file is missing, or older version.</summary>
613 + FileOlderVersion = 0x00000004,
614 +
615 + /// <summary>Reinstall if file is missing, or equal or older version.</summary>
616 + FileEqualVersion = 0x00000008,
617 +
618 + /// <summary>Reinstall if file is missing, or not exact version.</summary>
619 + FileExact = 0x00000010,
620 +
621 + /// <summary>Checksum executables, reinstall if missing or corrupt.</summary>
622 + FileVerify = 0x00000020,
623 +
624 + /// <summary>Reinstall all files, regardless of version.</summary>
625 + FileReplace = 0x00000040,
626 +
627 + /// <summary>Insure required machine reg entries.</summary>
628 + MachineData = 0x00000080,
629 +
630 + /// <summary>Insure required user reg entries.</summary>
631 + UserData = 0x00000100,
632 +
633 + /// <summary>Validate shortcuts items.</summary>
634 + Shortcut = 0x00000200,
635 +
636 + /// <summary>Use re-cache source install package.</summary>
637 + Package = 0x00000400,
638 + }
639 +
640 + /// <summary>
641 + /// Attributes for <see cref="Transaction"/> methods.
642 + /// </summary>
643 + [Flags]
644 + public enum TransactionAttributes : int
645 + {
646 + /// <summary>No attributes.</summary>
647 + None = 0x00000000,
648 +
649 + /// <summary>Request that the Windows Installer not shutdown the embedded UI until the transaction is complete.</summary>
650 + ChainEmbeddedUI = 0x00000001,
651 +
652 + /// <summary>Request that the Windows Installer transfer the embedded UI from the original installation.</summary>
653 + JoinExistingEmbeddedUI = 0x00000002,
654 + }
655 +
656 + /// <summary>
657 + /// Transform error conditions available for <see cref="Database.CreateTransformSummaryInfo"/> or
658 + /// <see cref="Database.ApplyTransform(string,TransformErrors)"/>.
659 + /// </summary>
660 + [Flags]
661 + public enum TransformErrors : int
662 + {
663 + /// <summary>No error conditions.</summary>
664 + None = 0x0000,
665 +
666 + /// <summary>Adding a row that already exists.</summary>
667 + AddExistingRow = 0x0001,
668 +
669 + /// <summary>Deleting a row that doesn't exist.</summary>
670 + DelMissingRow = 0x0002,
671 +
672 + /// <summary>Adding a table that already exists.</summary>
673 + AddExistingTable = 0x0004,
674 +
675 + /// <summary>Deleting a table that doesn't exist.</summary>
676 + DelMissingTable = 0x0008,
677 +
678 + /// <summary>Updating a row that doesn't exist.</summary>
679 + UpdateMissingRow = 0x0010,
680 +
681 + /// <summary>Transform and database code pages do not match and neither code page is neutral.</summary>
682 + ChangeCodePage = 0x0020,
683 +
684 + /// <summary>Create the temporary _TransformView table when applying the transform.</summary>
685 + ViewTransform = 0x0100,
686 + }
687 +
688 + /// <summary>
689 + /// Transform validation flags available for <see cref="Database.CreateTransformSummaryInfo"/>.
690 + /// </summary>
691 + [Flags]
692 + public enum TransformValidations : int
693 + {
694 + /// <summary>Validate no properties.</summary>
695 + None = 0x0000,
696 +
697 + /// <summary>Default language must match base database.</summary>
698 + Language = 0x0001,
699 +
700 + /// <summary>Product must match base database.</summary>
701 + Product = 0x0002,
702 +
703 + /// <summary>Check major version only.</summary>
704 + MajorVersion = 0x0008,
705 +
706 + /// <summary>Check major and minor versions only.</summary>
707 + MinorVersion = 0x0010,
708 +
709 + /// <summary>Check major, minor, and update versions.</summary>
710 + UpdateVersion = 0x0020,
711 +
712 + /// <summary>Installed version &lt; base version.</summary>
713 + NewLessBaseVersion = 0x0040,
714 +
715 + /// <summary>Installed version &lt;= base version.</summary>
716 + NewLessEqualBaseVersion = 0x0080,
717 +
718 + /// <summary>Installed version = base version.</summary>
719 + NewEqualBaseVersion = 0x0100,
720 +
721 + /// <summary>Installed version &gt;= base version.</summary>
722 + NewGreaterEqualBaseVersion = 0x0200,
723 +
724 + /// <summary>Installed version &gt; base version.</summary>
725 + NewGreaterBaseVersion = 0x0400,
726 +
727 + /// <summary>UpgradeCode must match base database.</summary>
728 + UpgradeCode = 0x0800,
729 + }
730 +
731 + /// <summary>
732 + /// Specifies the installation context for <see cref="ProductInstallation"/>s,
733 + /// <see cref="PatchInstallation"/>es, and
734 + /// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
735 + /// </summary>
736 + [Flags]
737 + public enum UserContexts : int
738 + {
739 + /// <summary>Not installed.</summary>
740 + None = 0,
741 +
742 + /// <summary>User managed install context.</summary>
743 + UserManaged = 1,
744 +
745 + /// <summary>User non-managed context.</summary>
746 + UserUnmanaged = 2,
747 +
748 + /// <summary>Per-machine context.</summary>
749 + Machine = 4,
750 +
751 + /// <summary>All contexts, or all valid values.</summary>
752 + All = (UserManaged | UserUnmanaged | Machine),
753 +
754 + /// <summary>All user-managed contexts.</summary>
755 + AllUserManaged = 8,
756 + }
757 +
758 + /// <summary>
759 + /// Defines the type of error encountered by the <see cref="View.Validate"/>, <see cref="View.ValidateNew"/>,
760 + /// or <see cref="View.ValidateFields"/> methods of the <see cref="View"/> class.
761 + /// </summary>
762 + public enum ValidationError : int
763 + {
764 + /*
765 + InvalidArg = -3,
766 + MoreData = -2,
767 + FunctionError = -1,
768 + */
769 +
770 + /// <summary>No error.</summary>
771 + None = 0,
772 +
773 + /// <summary>The new record duplicates primary keys of the existing record in a table.</summary>
774 + DuplicateKey = 1,
775 +
776 + /// <summary>There are no null values allowed, or the column is about to be deleted but is referenced by another row.</summary>
777 + Required = 2,
778 +
779 + /// <summary>The corresponding record in a foreign table was not found.</summary>
780 + BadLink = 3,
781 +
782 + /// <summary>The data is greater than the maximum value allowed.</summary>
783 + Overflow = 4,
784 +
785 + /// <summary>The data is less than the minimum value allowed.</summary>
786 + Underflow = 5,
787 +
788 + /// <summary>The data is not a member of the values permitted in the set.</summary>
789 + NotInSet = 6,
790 +
791 + /// <summary>An invalid version string was supplied.</summary>
792 + BadVersion = 7,
793 +
794 + /// <summary>The case was invalid. The case must be all uppercase or all lowercase.</summary>
795 + BadCase = 8,
796 +
797 + /// <summary>An invalid GUID was supplied.</summary>
798 + BadGuid = 9,
799 +
800 + /// <summary>An invalid wildcard file name was supplied, or the use of wildcards was invalid.</summary>
801 + BadWildcard = 10,
802 +
803 + /// <summary>An invalid identifier was supplied.</summary>
804 + BadIdentifier = 11,
805 +
806 + /// <summary>Invalid language IDs were supplied.</summary>
807 + BadLanguage = 12,
808 +
809 + /// <summary>An invalid file name was supplied.</summary>
810 + BadFileName = 13,
811 +
812 + /// <summary>An invalid path was supplied.</summary>
813 + BadPath = 14,
814 +
815 + /// <summary>An invalid conditional statement was supplied.</summary>
816 + BadCondition = 15,
817 +
818 + /// <summary>An invalid format string was supplied.</summary>
819 + BadFormatted = 16,
820 +
821 + /// <summary>An invalid template string was supplied.</summary>
822 + BadTemplate = 17,
823 +
824 + /// <summary>An invalid string was supplied in the DefaultDir column of the Directory table.</summary>
825 + BadDefaultDir = 18,
826 +
827 + /// <summary>An invalid registry path string was supplied.</summary>
828 + BadRegPath = 19,
829 +
830 + /// <summary>An invalid string was supplied in the CustomSource column of the CustomAction table.</summary>
831 + BadCustomSource = 20,
832 +
833 + /// <summary>An invalid property string was supplied.</summary>
834 + BadProperty = 21,
835 +
836 + /// <summary>The _Validation table is missing a reference to a column.</summary>
837 + MissingData = 22,
838 +
839 + /// <summary>The category column of the _Validation table for the column is invalid.</summary>
840 + BadCategory = 23,
841 +
842 + /// <summary>The table in the Keytable column of the _Validation table was not found or loaded.</summary>
843 + BadKeyTable = 24,
844 +
845 + /// <summary>The value in the MaxValue column of the _Validation table is less than the value in the MinValue column.</summary>
846 + BadMaxMinValues = 25,
847 +
848 + /// <summary>An invalid cabinet name was supplied.</summary>
849 + BadCabinet = 26,
850 +
851 + /// <summary>An invalid shortcut target name was supplied.</summary>
852 + BadShortcut = 27,
853 +
854 + /// <summary>The string is too long for the length specified by the column definition.</summary>
855 + StringOverflow = 28,
856 +
857 + /// <summary>An invalid localization attribute was supplied. (Primary keys cannot be localized.)</summary>
858 + BadLocalizeAttrib = 29
859 + }
860 +
861 + /// <summary>
862 + /// Specifies the modify mode for <see cref="View.Modify"/>.
863 + /// </summary>
864 + public enum ViewModifyMode : int
865 + {
866 + /// <summary>
867 + /// Refreshes the information in the supplied record without changing the position
868 + /// in the result set and without affecting subsequent fetch operations.
869 + /// </summary>
870 + Seek = -1,
871 +
872 + /// <summary>Refreshes the data in a Record.</summary>
873 + Refresh = 0,
874 +
875 + /// <summary>Inserts a Record into the view.</summary>
876 + Insert = 1,
877 +
878 + /// <summary>Updates the View with new data from the Record.</summary>
879 + Update = 2,
880 +
881 + /// <summary>Updates or inserts a Record into the View.</summary>
882 + Assign = 3,
883 +
884 + /// <summary>Updates or deletes and inserts a Record into the View.</summary>
885 + Replace = 4,
886 +
887 + /// <summary>Inserts or validates a record.</summary>
888 + Merge = 5,
889 +
890 + /// <summary>Deletes a Record from the View.</summary>
891 + Delete = 6,
892 +
893 + /// <summary>Inserts a Record into the View. The inserted data is not persistent.</summary>
894 + InsertTemporary = 7,
895 +
896 + /// <summary>Validates a record.</summary>
897 + Validate = 8,
898 +
899 + /// <summary>Validates a new record.</summary>
900 + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
901 + ValidateNew = 9,
902 +
903 + /// <summary>Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record.</summary>
904 + ValidateField = 10,
905 +
906 + /// <summary>Validates a record that will be deleted later.</summary>
907 + ValidateDelete = 11,
908 + }
909 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources
Binary files /dev/null and b/src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.resources differ
src/dtf/WixToolset.Dtf.WindowsInstaller/Errors.txt new
+404
@@ -0,0 +1,404 @@
1 +; Windows Installer Internal Error Messages
2 +;
3 +; Error numbers greater than 2000 are "internal errors" and do not have
4 +; localized strings in msimsg.dll. The messages are provided here
5 +; (in English only) because they can be informative and time-saving
6 +; during developement.
7 +;
8 +
9 +2101=Shortcuts not supported by the OS.
10 +2102=Invalid .INI action: [2]
11 +2103=Could not resolve path for shell folder [2].
12 +2104=Writing .INI file: [3]: System error: [2]
13 +2105=Shortcut Creation [3] Failed. System error: [2]
14 +2106=Shortcut Deletion [3] Failed. System error: [2]
15 +2107=Error [3] registering type library [2].
16 +2108=Error [3] unregistering type library [2].
17 +2109=Section missing for INI action.
18 +2110=Key missing for INI action.
19 +2111=Detection of running apps failed, could not get performance data. Reg operation returned : [2].
20 +2112=Detection of running apps failed, could not get performance index. Reg operation returned : [2].
21 +2113=Detection of running apps failed.
22 +2200=Database: [2]. Database object creation failed, mode = [3].
23 +2201=Database: [2]. Initialization failed, out of memory.
24 +2202=Database: [2]. Data access failed, out of memory.
25 +2203=Database: [2]. Cannot open database file. System error [3].
26 +2204=Database: [2]. Table already exists: [3]
27 +2205=Database: [2]. Table does not exist: [3]
28 +2206=Database: [2]. Table could not be dropped: [3]
29 +2207=Database: [2]. Intent violation.
30 +2208=Database: [2]. Insufficient parameters for Execute.
31 +2209=Database: [2]. Cursor in invalid state.
32 +2210=Database: [2]. Invalid update data type in column [3]
33 +2211=Database: [2]. Could not create database table [3]
34 +2212=Database: [2]. Database not in writable state.
35 +2213=Database: [2]. Error saving database tables.
36 +2214=Database: [2]. Error writing export file: [3]
37 +2215=Database: [2]. Cannot open import file: [3]
38 +2216=Database: [2]. Import file format error: [3], Line [4]
39 +2217=Database: [2]. Wrong state to CreateOutputDatabase [3].
40 +2218=Database: [2]. Table name not supplied.
41 +2219=Database: [2]. Invalid Installer database format.
42 +2220=Database: [2]. Invalid row/field data.
43 +2221=Database: [2]. Code page conflict in import file: [3].
44 +2222=Database: [2]. Transform or merge code page [3] differs from database code page [4].
45 +2223=Database: [2]. Databases are the same. No transform generated.
46 +2224=Database: [2]. GenerateTransform: Database corrupt. Table: [3]
47 +2225=Database: [2]. Transform: Cannot transform a temporary table. Table: [3]
48 +2226=Database: [2]. Transform failed.
49 +2227=Database: [2]. Invalid identifier '[3]' in SQL query: [4]
50 +2228=Database: [2]. Unknown table '[3]' in SQL query: [4]
51 +2229=Database: [2]. Could not load table '[3]' in SQL query: [4]
52 +2230=Database: [2]. Repeated table '[3]' in SQL query: [4]
53 +2231=Database: [2]. Missing ')' in SQL query: [3]
54 +2232=Database: [2]. Unexpected token '[3]' in SQL query: [4]
55 +2233=Database: [2]. No columns in SELECT clause in SQL query: [3]
56 +2234=Database: [2]. No columns in ORDER BY clause in SQL query: [3]
57 +2235=Database: [2]. Column '[3]' not present or ambiguous in SQL query: [4]
58 +2236=Database: [2]. Invalid operator '[3]' in SQL query: [4]
59 +2237=Database: [2]. Invalid or missing query string: [3]
60 +2238=Database: [2]. Missing FROM clause in SQL query: [3]
61 +2239=Database: [2]. Insufficient values in INSERT SQL stmt.
62 +2240=Database: [2]. Missing update columns in UPDATE SQL stmt.
63 +2241=Database: [2]. Missing insert columns in INSERT SQL stmt.
64 +2242=Database: [2]. Column '[3]' repeated
65 +2243=Database: [2]. No primary columns defined for table creation.
66 +2244=Database: [2]. Invalid type specifier '[3]' in SQL query [4].
67 +2245=IStorage::Stat failed with error [3]
68 +2246=Database: [2]. Invalid Installer transform format.
69 +2247=Database: [2] Transform stream read/write failure.
70 +2248=Database: [2] GenerateTransform/Merge: Column type in base table doesn't match reference table. Table: [3] Col #: [4]
71 +2249=Database: [2] GenerateTransform: More columns in base table than in reference table. Table: [3]
72 +2250=Database: [2] Transform: Cannot add existing row. Table: [3]
73 +2251=Database: [2] Transform: Cannot delete row that doesn't exist. Table: [3]
74 +2252=Database: [2] Transform: Cannot add existing table. Table: [3]
75 +2253=Database: [2] Transform: Cannot delete table that doesn't exist. Table: [3]
76 +2254=Database: [2] Transform: Cannot update row that doesn't exist. Table: [3]
77 +2255=Database: [2] Transform: Column with this name already exists. Table: [3] Col: [4]
78 +2256=Database: [2] GenerateTransform/Merge: Number of primary keys in base table doesn't match reference table. Table: [3]
79 +2257=Database: [2]. Intent to modify read only table: [3]
80 +2258=Database: [2]. Type mismatch in parameter: [3]
81 +2259=Database: [2] Table(s) Update failed.
82 +2260=Storage CopyTo failed. System error: [3]
83 +2261=Could not remove stream [2]. System error: [3]
84 +2262=Stream does not exist: [2]. System error: [3]
85 +2263=Could not open stream [2]. System error: [3]
86 +2264=Could not remove stream [2]. System error: [3]
87 +2265=Could not commit storage. System error: [3]
88 +2266=Could not rollback storage. System error: [3]
89 +2267=Could not delete storage [2]. System error: [3]
90 +2268=Database: [2]. Merge: There were merge conflicts reported in [3] tables.
91 +2269=Database: [2]. Merge: The column count differed in the '[3]' table of the two databases.
92 +2270=Database: [2]. GenerateTransform/Merge: Column name in base table doesn't match reference table. Table: [3] Col #: [4]
93 +2271=SummaryInformation write for transform failed.
94 +2272=Database: [2]. MergeDatabase will not write any changes because the database is open read-only.
95 +2273=Database: [2]. MergeDatabase: A reference to the base database was passed as the reference database.
96 +2274=Database: [2]. MergeDatabase: Unable to write errors to Error table. Could be due to a non-nullable column in a predefined Error table.
97 +2275=Database: [2]. Specified Modify [3] operation invalid for table joins.
98 +2276=Database: [2]. Code page [3] not supported by the system.
99 +2277=Database: [2]. Failed to save table [3].
100 +2278=Database: [2]. Exceeded number of expressions limit of 32 in WHERE clause of SQL query: [3].
101 +2279=Database: [2] Transform: Too many columns in base table [3]
102 +2280=Database: [2]. Could not create column [3] for table [4]
103 +2281=Could not rename stream [2]. System error: [3]
104 +2282=Stream name invalid [2].
105 +2302=Patch notify: [2] bytes patched to far.
106 +2303=Error getting volume info. GetLastError: [2]
107 +2304=Error getting disk free space. GetLastError: [2]. Volume: [3]
108 +2305=Error waiting for patch thread. GetLastError: [2].
109 +2306=Could not create thread for patch application. GetLastError: [2].
110 +2307=Source file key name is null.
111 +2308=Destination File Name is Null
112 +2309=Attempting to patch file [2] when patch already in progress.
113 +2310=Attempting to continue patch when no patch is in progress.
114 +2315=Missing Path Separator: [2]
115 +2318=File does not exist: [2]
116 +2319=Error setting file attribute: [3] GetLastError: [2]
117 +2320=File not writable: [2]
118 +2321=Error creating file: [2]
119 +2322=User canceled
120 +2323=Invalid File Attribute
121 +2324=Could not open file: [3] GetLastError: [2]
122 +2325=Could not get file time for file: [3] GetLastError: [2]
123 +2326=Error in FileToDosDateTime.
124 +2327=Could not remove directory: [3] GetLastError: [2]
125 +2328=Error getting file version info for file: [2]
126 +2329=Error deleting file: [3]. GetLastError: [2]
127 +2330=Error getting file attributes: [3]. GetLastError: [2]
128 +2331=Error loading library [2] or finding entry point [3]
129 +2332=Error getting file attributes. GetLastError: [2]
130 +2333=Error setting file attributes. GetLastError: [2]
131 +2334=Error converting file time to local time for file: [3]. GetLastError: [2]
132 +2335=Path: [2] is not a parent of [3]
133 +2336=Error creating temp file on path: [3]. GetLastError: [2]
134 +2337=Could not close file: [3] GetLastError: [2]
135 +2338=Could not update resource for file: [3] GetLastError: [2]
136 +2339=Could not set file time for file: [3] GetLastError: [2]
137 +2340=Could not update resource for file: [3], Missing Resource
138 +2341=Could not update resource for file: [3], Resource too large
139 +2342=Could not update resource for file: [3] GetLastError: [2]
140 +2343=Specified path is empty.
141 +2344=Could not find required file IMAGEHLP.DLL to validate file:[2]
142 +2345=[2]: File does not contain a valid checksum value.
143 +2347=User ignore
144 +2348=Error attempting to read from cabinet stream.
145 +2349=Copy Resumed With Different Info
146 +2350=FDI Server Error
147 +2351=File key '[2]' not found in cabinet '[3]'. The installation cannot continue.
148 +2352=Couldn't initialize cabinet file server. The required file 'Cabinet.dll' may be missing.
149 +2353=Not a cabinet
150 +2354=Cannot handle cabinet
151 +2355=Corrupt cabinet
152 +2356=Couldn't locate cabinet in stream: [2].
153 +2357=Cannot set attributes
154 +2358=Error determining whether file is in-use: [3]. GetLastError: [2]
155 +2359=Unable to create the target file - file may be in use.
156 +2360=progress tick.
157 +2361=Need next cabinet.
158 +2362=Folder not found: [2]
159 +2363=Could not enumerate subfolders for folder: [2]
160 +2364=Bad enumeration constant in CreateCopier call.
161 +2365=Could not BindImage exe file [2]
162 +2366=User Failure
163 +2367=User Abort.
164 +2368=Failed to get network resource information. Error [2], network path [3]. Extended error: network provider [5], error code [4], error description [6].
165 +2370=Invalid CRC checksum value for [2] file.{ Its header says [3] for checksum, its computed value is [4].}
166 +2371=Could not apply patch to file [2]. GetLastError: [3]
167 +2372=Patch file [2] is corrupt or of an invalid format. Attempting to patch file [3]. GetLastError: [4]
168 +2373=File [2] is not a valid patch file.
169 +2374=File [2] is not a valid destination file for patch file [3].
170 +2375=Unknown patching error: [2].
171 +2376=Cabinet not found.
172 +2379=Error opening file for read: [3] GetLastError: [2]
173 +2380=Error opening file for write: [3] GetLastError: [2]
174 +2381=Directory does not exist: [2]
175 +2382=Drive not ready: [2]
176 +2401=64-bit registry operation attempted on 32-bit operating system for key [2].
177 +2402=Out of memory.
178 +2501=Could not create rollback script enumerator
179 +2502=Called InstallFinalize when no install in progress.
180 +2503=Called RunScript when not marked in progress.
181 +2601=Invalid value for property [2]: '[3]'
182 +2602=The [2] table entry '[3]' has no associated entry in the Media table.
183 +2603=Duplicate Table Name [2]
184 +2604=[2] property undefined.
185 +2605=Could not find server [2] in [3] or [4].
186 +2606=Value of property [2] is not a valid full path: '[3]'.
187 +2607=Media table not found or empty (required for installation of files).
188 +2608=Could not create security descriptor for object. Error: '[2]'.
189 +2609=Attempt to migrate product settings before initialization.
190 +2611=The file [2] is marked as compressed, but the associated media entry does not specify a cabinet.
191 +2612=Stream not found in '[2]' column. Primary key: '[3]'.
192 +2613=RemoveExistingProducts action sequenced incorrectly.
193 +2614=Could not access IStorage object from installation package.
194 +2615=Skipped unregistration of Module [2] due to source resolution failure.
195 +2616=Companion file [2] parent missing.
196 +2617=Shared component [2] not found in Component table.
197 +2618=Isolated application component [2] not found in Component table.
198 +2619=Isolated components [2], [3] not part of same feature.
199 +2620=Key file of isolated application component [2] not in File table.
200 +2621=Resource DLL or Resource ID information for shortcut [2] set incorrectly.
201 +2701=The Component Table exceeds the acceptable tree depth of [2] levels.
202 +2702=A Feature Table record ([2]) references a non-existent parent in the Attributes field.
203 +2703=Property name for root source path not defined: [2]
204 +2704=Root directory property undefined: [2]
205 +2705=Invalid table: [2]; Could not be linked as tree.
206 +2706=Source paths not created. No path exists for entry [2] in Directory Table
207 +2707=Target paths not created. No path exists for entry [2] in Directory Table
208 +2708=No entries found in the file table.
209 +2709=The specified Component name ('[2]') not found in Component Table.
210 +2710=The requested 'Select' state is illegal for this Component.
211 +2711=The specified Feature name ('[2]') not found in Feature Table.
212 +2712=Invalid return from modeless dialog: [3], in action [2].
213 +2713=Null value in a non-nullable column ('[2]' in '[3]' column of the '[4]' table.
214 +2714=Invalid value for default folder name: [2].
215 +2715=The specified File key ('[2]') not found in the File Table.
216 +2716=Couldn't create a random subcomponent name for component '[2]'.
217 +2717=Bad action condition or error calling custom action '[2]'.
218 +2718=Missing package name for product code '[2]'.
219 +2719=Neither UNC nor drive letter path found in source '[2]'.
220 +2720=Error opening source list key. Error: '[2]'
221 +2721=Custom action [2] not found in Binary table stream
222 +2722=Custom action [2] not found in File table
223 +2723=Custom action [2] specifies unsupported type
224 +2724=The volume label '[2]' on the media you're running from doesn't match the label '[3]' given in the Media table. This is allowed only if you have only 1 entry in your Media table.
225 +2725=Invalid database tables
226 +2726=Action not found: [2]
227 +2727=The directory entry '[2]' does not exist in the Directory table
228 +2728=Table definition error: [2]
229 +2729=Install engine not initialized.
230 +2730=Bad value in database. Table: '[2]'; Primary key: '[3]'; Column: '[4]'
231 +2731=Selection Manager not initialized.
232 +2732=Directory Manager not initialized.
233 +2733=Bad foreign key ('[2]') in '[3]' column of the '[4]' table.
234 +2734=Invalid Reinstall mode character.
235 +2735=Custom action '[2]' has caused an unhandled exception and has been stopped. This may be the result of an internal error in the custom action, such as an access violation.
236 +2736=Generation of custom action temp file failed: [2]
237 +2737=Could not access custom action [2], entry [3], library [4]
238 +2738=Could not access VBScript runtime for custom action [2]
239 +2739=Could not access JavaScript runtime for custom action [2]
240 +2740=Custom action [2] script error [3], [4]: [5] Line [6], Column [7], [8]
241 +2741=Configuration information for product [2] is corrupt. Invalid info: [2]
242 +2742=Marshaling to Server failed: [2]
243 +2743=Could not execute custom action [2], location: [3], command: [4]
244 +2744=EXE failed called by custom action [2], location: [3], command: [4]
245 +2745=Transform [2] invalid for package [3]. Expected language [4], found language [5].
246 +2746=Transform [2] invalid for package [3]. Expected product [4], found product [5].
247 +2747=Transform [2] invalid for package [3]. Expected product version < [4], found product version [5].
248 +2748=Transform [2] invalid for package [3]. Expected product version <= [4], found product version [5].
249 +2749=Transform [2] invalid for package [3]. Expected product version == [4], found product version [5].
250 +2750=Transform [2] invalid for package [3]. Expected product version >= [4], found product version [5].
251 +2751=Transform [2] invalid for package [3]. Expected product version > [4], found product version [5].
252 +2752=Could not open transform [2] stored as child storage of package [4].
253 +2753=The File '[2]' is not marked for installation.
254 +2754=The File '[2]' is not a valid patch file.
255 +2755=Server returned unexpected error [2] attempting to install package [3].
256 +2756=The property '[2]' was used as a directory property in one or more tables, but no value was ever assigned.
257 +2757=Could not create summary info for transform [2].
258 +2758=Transform [2] doesn't contain a MSI version.
259 +2759=Transform [2] version [3] incompatible with engine; Min: [4], Max: [5].
260 +2760=Transform [2] invalid for package [3]. Expected upgrade code [4], found [5].
261 +2761=Cannot begin transaction. Global mutex not properly initialized.
262 +2762=Cannot write script record. Transaction not started.
263 +2763=Cannot run script. Transaction not started.
264 +2765=Assembly name missing from AssemblyName table : Component: [4].
265 +2766=The file [2] is an invalid MSI storage file.
266 +2767=No more data{ while enumerating [2]}.
267 +2768=Transform in patch package is invalid.
268 +2769=Custom Action [2] did not close [3] handles.
269 +2770=Cached folder [2] not defined in internal cache folder table.
270 +2771=Upgrade of feature [2] has a missing component.
271 +2772=New upgrade feature [2] must be a leaf feature.
272 +2801=Unknown Message -- Type [2]. No action is taken.
273 +2802=No publisher is found for the event [2].
274 +2803=Dialog View did not find a record for the dialog [2].
275 +2804=On activation of the control [3] on dialog [2], failed to evaluate the condition [3].
276 +2805=
277 +2806=The dialog [2] failed to evaluate the condition [3].
278 +2807=The action [2] is not recognized.
279 +2808=Default button is ill-defined on dialog [2].
280 +2809=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from [3] to [4], but there is no further pointer.
281 +2810=On the dialog [2] the next control pointers do not form a cycle. There is a pointer from both [3] and [5] to [4].
282 +2811=On dialog [2] control [3] has to take focus, but it is unable to do so.
283 +2812=The event [2] is not recognized.
284 +2813=The EndDialog event was called with the argument [2], but the dialog has a parent.
285 +2814=On the dialog [2] the control [3] names a non-existent control [4] as the next control.
286 +2815=ControlCondition table has a row without condition for the dialog [2].
287 +2816=The EventMapping table refers to an invalid control [4] on dialog [2] for the event [3].
288 +2817=The event [2] failed to set the attribute for the control [4] on dialog [3].
289 +2818=In the ControlEvent table EndDialog has an unrecognized argument [2].
290 +2819=Control [3] on dialog [2] needs a property linked to it.
291 +2820=Attempted to initialize an already initialized handler.
292 +2821=Attempted to initialize an already initialized dialog: [2].
293 +2822=No other method can be called on dialog [2] until all the controls are added.
294 +2823=Attempted to initialize an already initialized control: [3] on dialog [2].
295 +2824=The dialog attribute [3] needs a record of at least [2] field(s).
296 +2825=The control attribute [3] needs a record of at least [2] field(s).
297 +2826=Control [3] on dialog [2] extends beyond the boundaries of the dialog [4] by [5] pixels.
298 +2827=The button [4] on the radio button group [3] on dialog [2] extends beyond the boundaries of the group [5] by [6] pixels.
299 +2828=Tried to remove control [3] from dialog [2], but the control is not part of the dialog.
300 +2829=Attempt to use an uninitialized dialog.
301 +2830=Attempt to use an uninitialized control on dialog [2].
302 +2831=The control [3] on dialog [2] does not support [5] the attribute [4].
303 +2832=The dialog [2] does not support the attribute [3].
304 +2833=Control [4] on dialog [3] ignored the message [2].
305 +2834=The next pointers on the dialog [2] do not form a single loop.
306 +2835=The control [2] was not found on dialog [3].
307 +2836=The control [3] on the dialog [2] cannot take focus.
308 +2837=The control [3] on dialog [2] wants the win proc to return [4].
309 +2838=The item [2] in the selection table has itself as a parent.
310 +2839=Setting the property [2] failed.
311 +2840=Error dialog name mismatch.
312 +2841=No OK button was found on the error dialog
313 +2842=No text field was found on the error dialog.
314 +2843=The ErrorString attribute is not supported for standard dialogs.
315 +2844=Cannot execute an error dialog if the error string is not set.
316 +2845=The total width of the buttons exceeds the size of the error dialog.
317 +2846=SetFocus did not find the required control on the error dialog.
318 +2847=The control [3] on dialog [2] has both the icon and the bitmap style set.
319 +2848=Tried to set control [3] as the default button on dialog [2], but the control does not exist.
320 +2849=The control [3] on dialog [2] is of a type, that cannot be integer valued.
321 +2850=Unrecognized volume type.
322 +2851=The data for the icon [2] is not valid.
323 +2852=At least one control has to be added to dialog [2] before it is used.
324 +2853=Dialog [2] is a modeless dialog. The execute method should not be called on it.
325 +2854=On the dialog [2] the control [3] is designated as first active control, but there is no such control.
326 +2855=The radio button group [3] on dialog [2] has fewer than 2 buttons.
327 +2856=Creating a second copy of the dialog [2].
328 +2857=The directory [2] is mentioned in the selection table but not found.
329 +2858=The data for the bitmap [2] is not valid.
330 +2859=Test error message.
331 +2860=Cancel button is ill-defined on dialog [2].
332 +2861=The next pointers for the radio buttons on dialog [2] control [3] do not form a cycle.
333 +2862=The attributes for the control [3] on dialog [2] do not define a valid icon size. Setting the size to 16.
334 +2863=The control [3] on dialog [2] needs the icon [4] in size [5]x[5], but that size is not available. Loading the first available size.
335 +2864=The control [3] on dialog [2] received a browse event, but there is no configurable directory for the present selection. Likely cause: browse button is not authored correctly.
336 +2865=Control [3] on billboard [2] extends beyond the boundaries of the billboard [4] by [5] pixels.
337 +2866=The dialog [2] is not allowed to return the argument [3].
338 +2867=The error dialog property is not set.
339 +2868=The error dialog [2] does not have the error style bit set.
340 +2869=The dialog [2] has the error style bit set, but is not an error dialog.
341 +2870=The help string [4] for control [3] on dialog [2] does not contain the separator character.
342 +2871=The [2] table is out of date: [3]
343 +2872=The argument of the CheckPath control event on dialog [2] is invalid.
344 +2873=On the dialog [2] the control [3] has an invalid string length limit: [4]
345 +2874=Changing the text font to [2] failed.
346 +2875=Changing the text color to [2] failed.
347 +2876=The control [3] on dialog [2] had to truncate the string: [4]
348 +2877=The binary data [2] was not found.
349 +2878=On the dialog [2] the control [3] has a possible value: [4]. This is an invalid or duplicate value.
350 +2879=The control [3] on dialog [2] cannot parse the mask string: [4]
351 +2880=Do not perform the remaining control events.
352 +2881=Initialization failed.
353 +2882=Dialog window class registration failed.
354 +2883=CreateNewDialog failed for the dialog [2].
355 +2884=Failed to create a window for the dialog [2]!
356 +2885=Failed to create the control [3] on the dialog [2].
357 +2886=Creating the [2] table failed.
358 +2887=Creating a cursor to the [2] table failed.
359 +2888=Executing the [2] view failed.
360 +2889=Creating the window for the control [3] on dialog [2] failed.
361 +2890=The handler failed in creating an initialized dialog.
362 +2891=Failed to destroy window for dialog [2].
363 +2892=[2] is an integer only control, [3] is not a valid integer value.
364 +2893=The control [3] on dialog [2] can accept property values that are at most [5] characters long. The value [4] exceeds this limit, and has been truncated.
365 +2894=Loading RichEd20.dll failed. GetLastError() returned: [2]
366 +2895=Freeing RichEd20.dll failed. GetLastError() returned: [2]
367 +2896=Executing action [2] failed.
368 +2897=Failed to create any [2] font on this system.
369 +2898=For [2] text style, the system created a '[3]' font, in [4] character set.
370 +2899=Failed to create [2] text style. GetLastError() returned: [3].
371 +2901=Invalid parameter to operation [2]: Parameter [3]
372 +2902=Operation [2] called out of sequence.
373 +2903=The file [2] is missing.
374 +2904=Could not BindImage file [2].
375 +2905=Could not read record from script file [2].
376 +2906=Missing header in script file [2].
377 +2907=Could not create secure security descriptor. Error: [2]
378 +2908=Could not register component [2].
379 +2909=Could not unregister component [2].
380 +2910=Could not determine user's security id.
381 +2911=Could not remove the folder [2].
382 +2912=Could not schedule file [2] for removal on reboot.
383 +2919=No cabinet specified for compressed file: [2]
384 +2920=Source directory not specified for file [2].
385 +2924=Script [2] version unsupported. Script version: [3], minimum version: [4], maximum version: [5].
386 +2927=ShellFolder id [2] is invalid.
387 +2928=Exceeded maximum number of sources. Skipping source '[2]'.
388 +2929=Could not determine publishing root. Error: [2]
389 +2932=Could not create file [2] from script data. Error: [3]
390 +2933=Could not initialize rollback script [2].
391 +2934=Could not secure transform [2]. Error [3]
392 +2935=Could not un-secure transform [2]. Error [3]
393 +2936=Could not find transform [2].
394 +2937=The Windows Installer cannot install a system file protection catalog. Catalog: [2], Error: [3]
395 +2938=The Windows Installer cannot retrieve a system file protection catalog from the cache. Catalog: [2], Error: [3]
396 +2939=The Windows Installer cannot delete a system file protection catalog from the cache. Catalog: [2], Error: [3]
397 +2940=Directory Manager not supplied for source resolution.
398 +2941=Unable to compute the CRC for file [2].
399 +2942=BindImage action has not been executed on [2] file.
400 +2943=This version of Windows does not support deploying 64-bit packages. The script [2] is for a 64-bit package.
401 +2944=GetProductAssignmentType failed.
402 +2945=Installation of ComPlus App [2] failed with error [3].
403 +3001=The patches in this list contain incorrect sequencing information: [2][3][4][5][6][7][8][9][10][11][12][13][14][15][16]. 3.0
404 +3002=Patch [2] contains invalid sequencing information.
src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs new
+573
@@ -0,0 +1,573 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using System.Globalization;
10 + using System.Runtime.Serialization;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + /// <summary>
14 + /// Base class for Windows Installer exceptions.
15 + /// </summary>
16 + [Serializable]
17 + public class InstallerException : SystemException
18 + {
19 + private int errorCode;
20 + private object[] errorData;
21 +
22 + /// <summary>
23 + /// Creates a new InstallerException with a specified error message and a reference to the
24 + /// inner exception that is the cause of this exception.
25 + /// </summary>
26 + /// <param name="msg">The message that describes the error.</param>
27 + /// <param name="innerException">The exception that is the cause of the current exception. If the
28 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
29 + /// is raised in a catch block that handles the inner exception.</param>
30 + public InstallerException(string msg, Exception innerException)
31 + : this(0, msg, innerException)
32 + {
33 + }
34 +
35 + /// <summary>
36 + /// Creates a new InstallerException with a specified error message.
37 + /// </summary>
38 + /// <param name="msg">The message that describes the error.</param>
39 + public InstallerException(string msg)
40 + : this(0, msg)
41 + {
42 + }
43 +
44 + /// <summary>
45 + /// Creates a new InstallerException.
46 + /// </summary>
47 + public InstallerException()
48 + : this(0, null)
49 + {
50 + }
51 +
52 + internal InstallerException(int errorCode, string msg, Exception innerException)
53 + : base(msg, innerException)
54 + {
55 + this.errorCode = errorCode;
56 + this.SaveErrorRecord();
57 + }
58 +
59 + internal InstallerException(int errorCode, string msg)
60 + : this(errorCode, msg, null)
61 + {
62 + }
63 +
64 + /// <summary>
65 + /// Initializes a new instance of the InstallerException class with serialized data.
66 + /// </summary>
67 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
68 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
69 + protected InstallerException(SerializationInfo info, StreamingContext context) : base(info, context)
70 + {
71 + if (info == null)
72 + {
73 + throw new ArgumentNullException("info");
74 + }
75 +
76 + this.errorCode = info.GetInt32("msiErrorCode");
77 + }
78 +
79 + /// <summary>
80 + /// Gets the system error code that resulted in this exception, or 0 if not applicable.
81 + /// </summary>
82 + public int ErrorCode
83 + {
84 + get
85 + {
86 + return this.errorCode;
87 + }
88 + }
89 +
90 + /// <summary>
91 + /// Gets a message that describes the exception. This message may contain detailed
92 + /// formatted error data if it was available.
93 + /// </summary>
94 + public override String Message
95 + {
96 + get
97 + {
98 + string msg = base.Message;
99 + using (Record errorRec = this.GetErrorRecord())
100 + {
101 + if (errorRec != null)
102 + {
103 + string errorMsg = Installer.GetErrorMessage(errorRec, CultureInfo.InvariantCulture);
104 + msg = Combine(msg, errorMsg);
105 + }
106 + }
107 + return msg;
108 + }
109 + }
110 +
111 + /// <summary>
112 + /// Sets the SerializationInfo with information about the exception.
113 + /// </summary>
114 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 + {
118 + if (info == null)
119 + {
120 + throw new ArgumentNullException("info");
121 + }
122 +
123 + info.AddValue("msiErrorCode", this.errorCode);
124 + base.GetObjectData(info, context);
125 + }
126 +
127 + /// <summary>
128 + /// Gets extended information about the error, or null if no further information
129 + /// is available.
130 + /// </summary>
131 + /// <returns>A Record object. Field 1 of the Record contains the installer
132 + /// message code. Other fields contain data specific to the particular error.</returns>
133 + /// <remarks><p>
134 + /// If the record is passed to <see cref="Session.Message"/>, it is formatted
135 + /// by looking up the string in the current database. If there is no installation
136 + /// session, the formatted error message may be obtained by a query on the Error table using
137 + /// the error code, followed by a call to <see cref="Record.ToString()"/>.
138 + /// Alternatively, the standard MSI message can by retrieved by calling the
139 + /// <see cref="Installer.GetErrorMessage(Record,CultureInfo)"/> method.
140 + /// </p><p>
141 + /// The following methods and properties may report extended error data:
142 + /// <list type="bullet">
143 + /// <item><see cref="Database"/> (constructor)</item>
144 + /// <item><see cref="Database"/>.<see cref="Database.ApplyTransform(string,TransformErrors)"/></item>
145 + /// <item><see cref="Database"/>.<see cref="Database.Commit"/></item>
146 + /// <item><see cref="Database"/>.<see cref="Database.Execute(string,object[])"/></item>
147 + /// <item><see cref="Database"/>.<see cref="Database.ExecuteQuery(string,object[])"/></item>
148 + /// <item><see cref="Database"/>.<see cref="Database.ExecuteIntegerQuery(string,object[])"/></item>
149 + /// <item><see cref="Database"/>.<see cref="Database.ExecuteStringQuery(string,object[])"/></item>
150 + /// <item><see cref="Database"/>.<see cref="Database.Export"/></item>
151 + /// <item><see cref="Database"/>.<see cref="Database.ExportAll"/></item>
152 + /// <item><see cref="Database"/>.<see cref="Database.GenerateTransform"/></item>
153 + /// <item><see cref="Database"/>.<see cref="Database.Import"/></item>
154 + /// <item><see cref="Database"/>.<see cref="Database.ImportAll"/></item>
155 + /// <item><see cref="Database"/>.<see cref="Database.Merge(Database,string)"/></item>
156 + /// <item><see cref="Database"/>.<see cref="Database.OpenView"/></item>
157 + /// <item><see cref="Database"/>.<see cref="Database.SummaryInfo"/></item>
158 + /// <item><see cref="Database"/>.<see cref="Database.ViewTransform"/></item>
159 + /// <item><see cref="View"/>.<see cref="View.Assign"/></item>
160 + /// <item><see cref="View"/>.<see cref="View.Delete"/></item>
161 + /// <item><see cref="View"/>.<see cref="View.Execute(Record)"/></item>
162 + /// <item><see cref="View"/>.<see cref="View.Insert"/></item>
163 + /// <item><see cref="View"/>.<see cref="View.InsertTemporary"/></item>
164 + /// <item><see cref="View"/>.<see cref="View.Merge"/></item>
165 + /// <item><see cref="View"/>.<see cref="View.Modify"/></item>
166 + /// <item><see cref="View"/>.<see cref="View.Refresh"/></item>
167 + /// <item><see cref="View"/>.<see cref="View.Replace"/></item>
168 + /// <item><see cref="View"/>.<see cref="View.Seek"/></item>
169 + /// <item><see cref="View"/>.<see cref="View.Update"/></item>
170 + /// <item><see cref="View"/>.<see cref="View.Validate"/></item>
171 + /// <item><see cref="View"/>.<see cref="View.ValidateFields"/></item>
172 + /// <item><see cref="View"/>.<see cref="View.ValidateDelete"/></item>
173 + /// <item><see cref="View"/>.<see cref="View.ValidateNew"/></item>
174 + /// <item><see cref="SummaryInfo"/> (constructor)</item>
175 + /// <item><see cref="Record"/>.<see cref="Record.SetStream(int,string)"/></item>
176 + /// <item><see cref="Session"/>.<see cref="Session.SetInstallLevel"/></item>
177 + /// <item><see cref="Session"/>.<see cref="Session.GetSourcePath"/></item>
178 + /// <item><see cref="Session"/>.<see cref="Session.GetTargetPath"/></item>
179 + /// <item><see cref="Session"/>.<see cref="Session.SetTargetPath"/></item>
180 + /// <item><see cref="ComponentInfo"/>.<see cref="ComponentInfo.CurrentState"/></item>
181 + /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.CurrentState"/></item>
182 + /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.ValidStates"/></item>
183 + /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.GetCost"/></item>
184 + /// </list>
185 + /// </p><p>
186 + /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
187 + /// It is best that the handle be closed manually as soon as it is no longer
188 + /// needed, as leaving lots of unused handles open can degrade performance.
189 + /// </p><p>
190 + /// Win32 MSI API:
191 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlasterrorrecord.asp">MsiGetLastErrorRecord</a>
192 + /// </p></remarks>
193 + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
194 + public Record GetErrorRecord()
195 + {
196 + return this.errorData != null ? new Record(this.errorData) : null;
197 + }
198 +
199 + internal static Exception ExceptionFromReturnCode(uint errorCode)
200 + {
201 + return ExceptionFromReturnCode(errorCode, null);
202 + }
203 +
204 + internal static Exception ExceptionFromReturnCode(uint errorCode, string msg)
205 + {
206 + msg = Combine(GetSystemMessage(errorCode), msg);
207 + switch (errorCode)
208 + {
209 + case (uint) NativeMethods.Error.FILE_NOT_FOUND:
210 + case (uint) NativeMethods.Error.PATH_NOT_FOUND: return new FileNotFoundException(msg);
211 +
212 + case (uint) NativeMethods.Error.INVALID_PARAMETER:
213 + case (uint) NativeMethods.Error.DIRECTORY:
214 + case (uint) NativeMethods.Error.UNKNOWN_PROPERTY:
215 + case (uint) NativeMethods.Error.UNKNOWN_PRODUCT:
216 + case (uint) NativeMethods.Error.UNKNOWN_FEATURE:
217 + case (uint) NativeMethods.Error.UNKNOWN_COMPONENT: return new ArgumentException(msg);
218 +
219 + case (uint) NativeMethods.Error.BAD_QUERY_SYNTAX: return new BadQuerySyntaxException(msg);
220 +
221 + case (uint) NativeMethods.Error.INVALID_HANDLE_STATE:
222 + case (uint) NativeMethods.Error.INVALID_HANDLE:
223 + InvalidHandleException ihex = new InvalidHandleException(msg);
224 + ihex.errorCode = (int) errorCode;
225 + return ihex;
226 +
227 + case (uint) NativeMethods.Error.INSTALL_USEREXIT: return new InstallCanceledException(msg);
228 +
229 + case (uint) NativeMethods.Error.CALL_NOT_IMPLEMENTED: return new NotImplementedException(msg);
230 +
231 + default: return new InstallerException((int) errorCode, msg);
232 + }
233 + }
234 +
235 + internal static string GetSystemMessage(uint errorCode)
236 + {
237 + const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
238 + const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
239 +
240 + StringBuilder buf = new StringBuilder(1024);
241 + uint formatCount = NativeMethods.FormatMessage(
242 + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
243 + IntPtr.Zero,
244 + (uint) errorCode,
245 + 0,
246 + buf,
247 + (uint) buf.Capacity,
248 + IntPtr.Zero);
249 +
250 + if (formatCount != 0)
251 + {
252 + return buf.ToString().Trim();
253 + }
254 + else
255 + {
256 + return null;
257 + }
258 + }
259 +
260 + internal void SaveErrorRecord()
261 + {
262 + // TODO: pass an affinity handle here?
263 + int recordHandle = RemotableNativeMethods.MsiGetLastErrorRecord(0);
264 + if (recordHandle != 0)
265 + {
266 + using (Record errorRec = new Record((IntPtr) recordHandle, true, null))
267 + {
268 + this.errorData = new object[errorRec.FieldCount];
269 + for (int i = 0; i < this.errorData.Length; i++)
270 + {
271 + this.errorData[i] = errorRec[i + 1];
272 + }
273 + }
274 + }
275 + else
276 + {
277 + this.errorData = null;
278 + }
279 + }
280 +
281 + private static string Combine(string msg1, string msg2)
282 + {
283 + if (msg1 == null) return msg2;
284 + if (msg2 == null) return msg1;
285 + return msg1 + " " + msg2;
286 + }
287 + }
288 +
289 + /// <summary>
290 + /// User Canceled the installation.
291 + /// </summary>
292 + [Serializable]
293 + public class InstallCanceledException : InstallerException
294 + {
295 + /// <summary>
296 + /// Creates a new InstallCanceledException with a specified error message and a reference to the
297 + /// inner exception that is the cause of this exception.
298 + /// </summary>
299 + /// <param name="msg">The message that describes the error.</param>
300 + /// <param name="innerException">The exception that is the cause of the current exception. If the
301 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
302 + /// is raised in a catch block that handles the inner exception.</param>
303 + public InstallCanceledException(string msg, Exception innerException)
304 + : base((int) NativeMethods.Error.INSTALL_USEREXIT, msg, innerException)
305 + {
306 + }
307 +
308 + /// <summary>
309 + /// Creates a new InstallCanceledException with a specified error message.
310 + /// </summary>
311 + /// <param name="msg">The message that describes the error.</param>
312 + public InstallCanceledException(string msg)
313 + : this(msg, null)
314 + {
315 + }
316 +
317 + /// <summary>
318 + /// Creates a new InstallCanceledException.
319 + /// </summary>
320 + public InstallCanceledException()
321 + : this(null, null)
322 + {
323 + }
324 +
325 + /// <summary>
326 + /// Initializes a new instance of the InstallCanceledException class with serialized data.
327 + /// </summary>
328 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
329 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
330 + protected InstallCanceledException(SerializationInfo info, StreamingContext context)
331 + : base(info, context)
332 + {
333 + }
334 + }
335 +
336 + /// <summary>
337 + /// A bad SQL query string was passed to <see cref="Database.OpenView"/> or <see cref="Database.Execute(string,object[])"/>.
338 + /// </summary>
339 + [Serializable]
340 + public class BadQuerySyntaxException : InstallerException
341 + {
342 + /// <summary>
343 + /// Creates a new BadQuerySyntaxException with a specified error message and a reference to the
344 + /// inner exception that is the cause of this exception.
345 + /// </summary>
346 + /// <param name="msg">The message that describes the error.</param>
347 + /// <param name="innerException">The exception that is the cause of the current exception. If the
348 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
349 + /// is raised in a catch block that handles the inner exception.</param>
350 + public BadQuerySyntaxException(string msg, Exception innerException)
351 + : base((int) NativeMethods.Error.BAD_QUERY_SYNTAX, msg, innerException)
352 + {
353 + }
354 +
355 + /// <summary>
356 + /// Creates a new BadQuerySyntaxException with a specified error message.
357 + /// </summary>
358 + /// <param name="msg">The message that describes the error.</param>
359 + public BadQuerySyntaxException(string msg)
360 + : this(msg, null)
361 + {
362 + }
363 +
364 + /// <summary>
365 + /// Creates a new BadQuerySyntaxException.
366 + /// </summary>
367 + public BadQuerySyntaxException()
368 + : this(null, null)
369 + {
370 + }
371 +
372 + /// <summary>
373 + /// Initializes a new instance of the BadQuerySyntaxException class with serialized data.
374 + /// </summary>
375 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
376 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
377 + protected BadQuerySyntaxException(SerializationInfo info, StreamingContext context)
378 + : base(info, context)
379 + {
380 + }
381 + }
382 +
383 + /// <summary>
384 + /// A method was called on an invalid installer handle. The handle may have been already closed.
385 + /// </summary>
386 + [Serializable]
387 + public class InvalidHandleException : InstallerException
388 + {
389 + /// <summary>
390 + /// Creates a new InvalidHandleException with a specified error message and a reference to the
391 + /// inner exception that is the cause of this exception.
392 + /// </summary>
393 + /// <param name="msg">The message that describes the error.</param>
394 + /// <param name="innerException">The exception that is the cause of the current exception. If the
395 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
396 + /// is raised in a catch block that handles the inner exception.</param>
397 + public InvalidHandleException(string msg, Exception innerException)
398 + : base((int) NativeMethods.Error.INVALID_HANDLE, msg, innerException)
399 + {
400 + }
401 +
402 + /// <summary>
403 + /// Creates a new InvalidHandleException with a specified error message.
404 + /// </summary>
405 + /// <param name="msg">The message that describes the error.</param>
406 + public InvalidHandleException(string msg)
407 + : this(msg, null)
408 + {
409 + }
410 +
411 + /// <summary>
412 + /// Creates a new InvalidHandleException.
413 + /// </summary>
414 + public InvalidHandleException()
415 + : this(null, null)
416 + {
417 + }
418 +
419 + /// <summary>
420 + /// Initializes a new instance of the InvalidHandleException class with serialized data.
421 + /// </summary>
422 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
423 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
424 + protected InvalidHandleException(SerializationInfo info, StreamingContext context)
425 + : base(info, context)
426 + {
427 + }
428 + }
429 +
430 + /// <summary>
431 + /// A failure occurred when executing <see cref="Database.Merge(Database,string)"/>. The exception may contain
432 + /// details about the merge conflict.
433 + /// </summary>
434 + [Serializable]
435 + public class MergeException : InstallerException
436 + {
437 + private IList<string> conflictTables;
438 + private IList<int> conflictCounts;
439 +
440 + /// <summary>
441 + /// Creates a new MergeException with a specified error message and a reference to the
442 + /// inner exception that is the cause of this exception.
443 + /// </summary>
444 + /// <param name="msg">The message that describes the error.</param>
445 + /// <param name="innerException">The exception that is the cause of the current exception. If the
446 + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
447 + /// is raised in a catch block that handles the inner exception.</param>
448 + public MergeException(string msg, Exception innerException)
449 + : base(msg, innerException)
450 + {
451 + }
452 +
453 + /// <summary>
454 + /// Creates a new MergeException with a specified error message.
455 + /// </summary>
456 + /// <param name="msg">The message that describes the error.</param>
457 + public MergeException(string msg)
458 + : base(msg)
459 + {
460 + }
461 +
462 + /// <summary>
463 + /// Creates a new MergeException.
464 + /// </summary>
465 + public MergeException()
466 + : base()
467 + {
468 + }
469 +
470 + internal MergeException(Database db, string conflictsTableName)
471 + : base("Merge failed.")
472 + {
473 + if (conflictsTableName != null)
474 + {
475 + IList<string> conflictTableList = new List<string>();
476 + IList<int> conflictCountList = new List<int>();
477 +
478 + using (View view = db.OpenView("SELECT `Table`, `NumRowMergeConflicts` FROM `" + conflictsTableName + "`"))
479 + {
480 + view.Execute();
481 +
482 + foreach (Record rec in view) using (rec)
483 + {
484 + conflictTableList.Add(rec.GetString(1));
485 + conflictCountList.Add((int) rec.GetInteger(2));
486 + }
487 + }
488 +
489 + this.conflictTables = conflictTableList;
490 + this.conflictCounts = conflictCountList;
491 + }
492 + }
493 +
494 + /// <summary>
495 + /// Initializes a new instance of the MergeException class with serialized data.
496 + /// </summary>
497 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
498 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
499 + protected MergeException(SerializationInfo info, StreamingContext context) : base(info, context)
500 + {
501 + if (info == null)
502 + {
503 + throw new ArgumentNullException("info");
504 + }
505 +
506 + this.conflictTables = (string[]) info.GetValue("mergeConflictTables", typeof(string[]));
507 + this.conflictCounts = (int[]) info.GetValue("mergeConflictCounts", typeof(int[]));
508 + }
509 +
510 + /// <summary>
511 + /// Gets the number of merge conflicts in each table, corresponding to the tables returned by
512 + /// <see cref="ConflictTables"/>.
513 + /// </summary>
514 + public IList<int> ConflictCounts
515 + {
516 + get
517 + {
518 + return new List<int>(this.conflictCounts);
519 + }
520 + }
521 +
522 + /// <summary>
523 + /// Gets the list of tables containing merge conflicts.
524 + /// </summary>
525 + public IList<string> ConflictTables
526 + {
527 + get
528 + {
529 + return new List<string>(this.conflictTables);
530 + }
531 + }
532 +
533 + /// <summary>
534 + /// Gets a message that describes the merge conflits.
535 + /// </summary>
536 + public override String Message
537 + {
538 + get
539 + {
540 + StringBuilder msg = new StringBuilder(base.Message);
541 + if (this.conflictTables != null)
542 + {
543 + for (int i = 0; i < this.conflictTables.Count; i++)
544 + {
545 + msg.Append(i == 0 ? " Conflicts: " : ", ");
546 + msg.Append(this.conflictTables[i]);
547 + msg.Append('(');
548 + msg.Append(this.conflictCounts[i]);
549 + msg.Append(')');
550 + }
551 + }
552 + return msg.ToString();
553 + }
554 + }
555 +
556 + /// <summary>
557 + /// Sets the SerializationInfo with information about the exception.
558 + /// </summary>
559 + /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
560 + /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
561 + public override void GetObjectData(SerializationInfo info, StreamingContext context)
562 + {
563 + if (info == null)
564 + {
565 + throw new ArgumentNullException("info");
566 + }
567 +
568 + info.AddValue("mergeConflictTables", this.conflictTables);
569 + info.AddValue("mergeConflictCounts", this.conflictCounts);
570 + base.GetObjectData(info, context);
571 + }
572 + }
573 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ExternalUIHandler.cs new
+223
@@ -0,0 +1,223 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections;
7 + using System.Runtime.InteropServices;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + /// <summary>
11 + /// Defines a callback function that the installer calls for progress notification and error messages.
12 + /// </summary>
13 + public delegate MessageResult ExternalUIHandler(
14 + InstallMessage messageType,
15 + string message,
16 + MessageButtons buttons,
17 + MessageIcon icon,
18 + MessageDefaultButton defaultButton);
19 +
20 + /// <summary>
21 + /// [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages.
22 + /// </summary>
23 + public delegate MessageResult ExternalUIRecordHandler(
24 + InstallMessage messageType,
25 + Record messageRecord,
26 + MessageButtons buttons,
27 + MessageIcon icon,
28 + MessageDefaultButton defaultButton);
29 +
30 + internal delegate int NativeExternalUIHandler(IntPtr context, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
31 +
32 + internal delegate int NativeExternalUIRecordHandler(IntPtr context, int messageType, int recordHandle);
33 +
34 + internal class ExternalUIProxy
35 + {
36 + private ExternalUIHandler handler;
37 +
38 + internal ExternalUIProxy(ExternalUIHandler handler)
39 + {
40 + this.handler = handler;
41 + }
42 +
43 + public ExternalUIHandler Handler
44 + {
45 + get { return this.handler; }
46 + }
47 +
48 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
49 + public int ProxyHandler(IntPtr contextPtr, int messageType, [MarshalAs(UnmanagedType.LPWStr)] string message)
50 + {
51 + try
52 + {
53 + int msgType = messageType & 0x7F000000;
54 + int buttons = messageType & 0x0000000F;
55 + int icon = messageType & 0x000000F0;
56 + int defButton = messageType & 0x00000F00;
57 +
58 + return (int) this.handler(
59 + (InstallMessage) msgType,
60 + message,
61 + (MessageButtons) buttons,
62 + (MessageIcon) icon,
63 + (MessageDefaultButton) defButton);
64 + }
65 + catch
66 + {
67 + return (int) MessageResult.Error;
68 + }
69 + }
70 + }
71 +
72 + internal class ExternalUIRecordProxy
73 + {
74 + private ExternalUIRecordHandler handler;
75 +
76 + internal ExternalUIRecordProxy(ExternalUIRecordHandler handler)
77 + {
78 + this.handler = handler;
79 + }
80 +
81 + public ExternalUIRecordHandler Handler
82 + {
83 + get { return this.handler; }
84 + }
85 +
86 + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
87 + public int ProxyHandler(IntPtr contextPtr, int messageType, int recordHandle)
88 + {
89 + try
90 + {
91 + int msgType = messageType & 0x7F000000;
92 + int buttons = messageType & 0x0000000F;
93 + int icon = messageType & 0x000000F0;
94 + int defButton = messageType & 0x00000F00;
95 +
96 + Record msgRec = (recordHandle != 0 ? Record.FromHandle((IntPtr) recordHandle, false) : null);
97 + using (msgRec)
98 + {
99 + return (int) this.handler(
100 + (InstallMessage) msgType,
101 + msgRec,
102 + (MessageButtons) buttons,
103 + (MessageIcon) icon,
104 + (MessageDefaultButton) defButton);
105 + }
106 + }
107 + catch
108 + {
109 + return (int) MessageResult.Error;
110 + }
111 + }
112 + }
113 +
114 + public static partial class Installer
115 + {
116 + private static IList externalUIHandlers = ArrayList.Synchronized(new ArrayList());
117 +
118 + /// <summary>
119 + /// Enables an external user-interface handler. This external UI handler is called before the
120 + /// normal internal user-interface handler. The external UI handler has the option to suppress
121 + /// the internal UI by returning a non-zero value to indicate that it has handled the messages.
122 + /// </summary>
123 + /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
124 + /// <param name="messageFilter">Specifies which messages to handle using the external message handler.
125 + /// If the external handler returns a non-zero result, then that message will not be sent to the UI,
126 + /// instead the message will be logged if logging has been enabled.</param>
127 + /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
128 + /// <remarks><p>
129 + /// To restore the previous UI handler, a second call is made to SetExternalUI using the
130 + /// ExternalUIHandler returned by the first call to SetExternalUI and specifying
131 + /// <see cref="InstallLogModes.None"/> as the message filter.
132 + /// </p><p>
133 + /// The external user interface handler does not have full control over the external user
134 + /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
135 + /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
136 + /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not
137 + /// handled by the external user interface handler is handled by Windows Installer. The initial
138 + /// "Preparing to install..." dialog always appears even if the external user interface
139 + /// handler handles all messages.
140 + /// </p><p>
141 + /// SetExternalUI should only be called from a bootstrapping application. You cannot call
142 + /// it from a custom action
143 + /// </p><p>
144 + /// Win32 MSI API:
145 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternalui.asp">MsiSetExternalUI</a>
146 + /// </p></remarks>
147 + public static ExternalUIHandler SetExternalUI(ExternalUIHandler uiHandler, InstallLogModes messageFilter)
148 + {
149 + NativeExternalUIHandler nativeHandler = null;
150 + if (uiHandler != null)
151 + {
152 + nativeHandler = new ExternalUIProxy(uiHandler).ProxyHandler;
153 + Installer.externalUIHandlers.Add(nativeHandler);
154 + }
155 + NativeExternalUIHandler oldNativeHandler = NativeMethods.MsiSetExternalUI(nativeHandler, (uint) messageFilter, IntPtr.Zero);
156 + if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIProxy)
157 + {
158 + Installer.externalUIHandlers.Remove(oldNativeHandler);
159 + return ((ExternalUIProxy) oldNativeHandler.Target).Handler;
160 + }
161 + else
162 + {
163 + return null;
164 + }
165 + }
166 +
167 + /// <summary>
168 + /// [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called
169 + /// before the normal internal user-interface handler. The external UI handler has the option to suppress
170 + /// the internal UI by returning a non-zero value to indicate that it has handled the messages.
171 + /// </summary>
172 + /// <param name="uiHandler">A callback delegate that handles the UI messages</param>
173 + /// <param name="messageFilter">Specifies which messages to handle using the external message handler.
174 + /// If the external handler returns a non-zero result, then that message will not be sent to the UI,
175 + /// instead the message will be logged if logging has been enabled.</param>
176 + /// <returns>The previously set external handler, or null if there was no previously set handler</returns>
177 + /// <remarks><p>
178 + /// To restore the previous UI handler, a second call is made to SetExternalUI using the
179 + /// ExternalUIHandler returned by the first call to SetExternalUI and specifying
180 + /// <see cref="InstallLogModes.None"/> as the message filter.
181 + /// </p><p>
182 + /// The external user interface handler does not have full control over the external user
183 + /// interface unless <see cref="SetInternalUI(InstallUIOptions)"/> is called with the uiLevel parameter set to
184 + /// <see cref="InstallUIOptions.Silent"/>. If SetInternalUI is not called, the internal user
185 + /// interface level defaults to <see cref="InstallUIOptions.Basic"/>. As a result, any message not
186 + /// handled by the external user interface handler is handled by Windows Installer. The initial
187 + /// "Preparing to install..." dialog always appears even if the external user interface
188 + /// handler handles all messages.
189 + /// </p><p>
190 + /// SetExternalUI should only be called from a bootstrapping application. You cannot call
191 + /// it from a custom action
192 + /// </p><p>
193 + /// Win32 MSI API:
194 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetexternaluirecord.asp">MsiSetExternalUIRecord</a>
195 + /// </p></remarks>
196 + public static ExternalUIRecordHandler SetExternalUI(ExternalUIRecordHandler uiHandler, InstallLogModes messageFilter)
197 + {
198 + NativeExternalUIRecordHandler nativeHandler = null;
199 + if (uiHandler != null)
200 + {
201 + nativeHandler = new ExternalUIRecordProxy(uiHandler).ProxyHandler;
202 + Installer.externalUIHandlers.Add(nativeHandler);
203 + }
204 + NativeExternalUIRecordHandler oldNativeHandler;
205 + uint ret = NativeMethods.MsiSetExternalUIRecord(nativeHandler, (uint) messageFilter, IntPtr.Zero, out oldNativeHandler);
206 + if (ret != 0)
207 + {
208 + Installer.externalUIHandlers.Remove(nativeHandler);
209 + throw InstallerException.ExceptionFromReturnCode(ret);
210 + }
211 +
212 + if (oldNativeHandler != null && oldNativeHandler.Target is ExternalUIRecordProxy)
213 + {
214 + Installer.externalUIHandlers.Remove(oldNativeHandler);
215 + return ((ExternalUIRecordProxy) oldNativeHandler.Target).Handler;
216 + }
217 + else
218 + {
219 + return null;
220 + }
221 + }
222 + }
223 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInfo.cs new
+497
@@ -0,0 +1,497 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections;
8 + using System.Collections.Generic;
9 +
10 + /// <summary>
11 + /// Accessor for information about features within the context of an installation session.
12 + /// </summary>
13 + public sealed class FeatureInfoCollection : ICollection<FeatureInfo>
14 + {
15 + private Session session;
16 +
17 + internal FeatureInfoCollection(Session session)
18 + {
19 + this.session = session;
20 + }
21 +
22 + /// <summary>
23 + /// Gets information about a feature within the context of an installation session.
24 + /// </summary>
25 + /// <param name="feature">name of the feature</param>
26 + /// <returns>feature object</returns>
27 + public FeatureInfo this[string feature]
28 + {
29 + get
30 + {
31 + return new FeatureInfo(this.session, feature);
32 + }
33 + }
34 +
35 + void ICollection<FeatureInfo>.Add(FeatureInfo item)
36 + {
37 + throw new InvalidOperationException();
38 + }
39 +
40 + void ICollection<FeatureInfo>.Clear()
41 + {
42 + throw new InvalidOperationException();
43 + }
44 +
45 + /// <summary>
46 + /// Checks if the collection contains a feature.
47 + /// </summary>
48 + /// <param name="feature">name of the feature</param>
49 + /// <returns>true if the feature is in the collection, else false</returns>
50 + public bool Contains(string feature)
51 + {
52 + return this.session.Database.CountRows(
53 + "Feature", "`Feature` = '" + feature + "'") == 1;
54 + }
55 +
56 + bool ICollection<FeatureInfo>.Contains(FeatureInfo item)
57 + {
58 + return item != null && this.Contains(item.Name);
59 + }
60 +
61 + /// <summary>
62 + /// Copies the features into an array.
63 + /// </summary>
64 + /// <param name="array">array that receives the features</param>
65 + /// <param name="arrayIndex">offset into the array</param>
66 + public void CopyTo(FeatureInfo[] array, int arrayIndex)
67 + {
68 + foreach (FeatureInfo feature in this)
69 + {
70 + array[arrayIndex++] = feature;
71 + }
72 + }
73 +
74 + /// <summary>
75 + /// Gets the number of features defined for the product.
76 + /// </summary>
77 + public int Count
78 + {
79 + get
80 + {
81 + return this.session.Database.CountRows("Feature");
82 + }
83 + }
84 +
85 + bool ICollection<FeatureInfo>.IsReadOnly
86 + {
87 + get
88 + {
89 + return true;
90 + }
91 + }
92 +
93 + bool ICollection<FeatureInfo>.Remove(FeatureInfo item)
94 + {
95 + throw new InvalidOperationException();
96 + }
97 +
98 + /// <summary>
99 + /// Enumerates the features in the collection.
100 + /// </summary>
101 + /// <returns>an enumerator over all features in the collection</returns>
102 + public IEnumerator<FeatureInfo> GetEnumerator()
103 + {
104 + using (View featureView = this.session.Database.OpenView(
105 + "SELECT `Feature` FROM `Feature`"))
106 + {
107 + featureView.Execute();
108 +
109 + foreach (Record featureRec in featureView) using (featureRec)
110 + {
111 + string feature = featureRec.GetString(1);
112 + yield return new FeatureInfo(this.session, feature);
113 + }
114 + }
115 + }
116 +
117 + IEnumerator IEnumerable.GetEnumerator()
118 + {
119 + return this.GetEnumerator();
120 + }
121 + }
122 +
123 + /// <summary>
124 + /// Provides access to information about a feature within the context of an installation session.
125 + /// </summary>
126 + public class FeatureInfo
127 + {
128 + private Session session;
129 + private string name;
130 +
131 + internal FeatureInfo(Session session, string name)
132 + {
133 + this.session = session;
134 + this.name = name;
135 + }
136 +
137 + /// <summary>
138 + /// Gets the name of the feature (primary key in the Feature table).
139 + /// </summary>
140 + public string Name
141 + {
142 + get
143 + {
144 + return this.name;
145 + }
146 + }
147 +
148 + /// <summary>
149 + /// Gets the current install state of the feature.
150 + /// </summary>
151 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
152 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
153 + /// <remarks><p>
154 + /// Win32 MSI API:
155 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a>
156 + /// </p></remarks>
157 + public InstallState CurrentState
158 + {
159 + get
160 + {
161 + int installState, actionState;
162 + uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState);
163 + if (ret != 0)
164 + {
165 + if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
166 + {
167 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
168 + }
169 + else
170 + {
171 + throw InstallerException.ExceptionFromReturnCode(ret);
172 + }
173 + }
174 +
175 + if (installState == (int) InstallState.Advertised)
176 + {
177 + return InstallState.Advertised;
178 + }
179 + return (InstallState) installState;
180 + }
181 + }
182 +
183 + /// <summary>
184 + /// Gets or sets the action state of the feature.
185 + /// </summary>
186 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
187 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
188 + /// <remarks><p>
189 + /// When changing the feature action, the action state of all the Components linked to the changed
190 + /// Feature records are also updated appropriately, based on the new feature Select state.
191 + /// All Features can be configured at once by specifying the keyword ALL instead of a specific feature name.
192 + /// </p><p>
193 + /// Win32 MSI APIs:
194 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturestate.asp">MsiGetFeatureState</a>,
195 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeaturestate.asp">MsiSetFeatureState</a>
196 + /// </p></remarks>
197 + public InstallState RequestState
198 + {
199 + get
200 + {
201 + int installState, actionState;
202 + uint ret = RemotableNativeMethods.MsiGetFeatureState((int) this.session.Handle, this.name, out installState, out actionState);
203 + if (ret != 0)
204 + {
205 + if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
206 + {
207 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
208 + }
209 + else
210 + {
211 + throw InstallerException.ExceptionFromReturnCode(ret);
212 + }
213 + }
214 + return (InstallState) actionState;
215 + }
216 +
217 + set
218 + {
219 + uint ret = RemotableNativeMethods.MsiSetFeatureState((int) this.session.Handle, this.name, (int) value);
220 + if (ret != 0)
221 + {
222 + if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
223 + {
224 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
225 + }
226 + else
227 + {
228 + throw InstallerException.ExceptionFromReturnCode(ret);
229 + }
230 + }
231 + }
232 + }
233 +
234 + /// <summary>
235 + /// Gets a list of valid installation states for the feature.
236 + /// </summary>
237 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
238 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
239 + /// <remarks><p>
240 + /// Win32 MSI API:
241 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturevalidstates.asp">MsiGetFeatureValidStates</a>
242 + /// </p></remarks>
243 + public ICollection<InstallState> ValidStates
244 + {
245 + get
246 + {
247 + List<InstallState> states = new List<InstallState>();
248 + uint installState;
249 + uint ret = RemotableNativeMethods.MsiGetFeatureValidStates((int) this.session.Handle, this.name, out installState);
250 + if (ret != 0)
251 + {
252 + if (ret == (uint) NativeMethods.Error.UNKNOWN_FEATURE)
253 + {
254 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
255 + }
256 + else
257 + {
258 + throw InstallerException.ExceptionFromReturnCode(ret);
259 + }
260 + }
261 +
262 + for (int i = 1; i <= (int) InstallState.Default; i++)
263 + {
264 + if (((int) installState & (1 << i)) != 0)
265 + {
266 + states.Add((InstallState) i);
267 + }
268 + }
269 + return states.AsReadOnly();
270 + }
271 + }
272 +
273 + /// <summary>
274 + /// Gets or sets the attributes of the feature.
275 + /// </summary>
276 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
277 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
278 + /// <remarks><p>
279 + /// Win32 MSI APIs:
280 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>,
281 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a>
282 + /// </p><p>
283 + /// Since the lpAttributes paramter of
284 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
285 + /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, this flag will
286 + /// not be retrieved.
287 + /// </p><p>
288 + /// Since the dwAttributes parameter of
289 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetfeatureattributes.asp">MsiSetFeatureAttributes</a>
290 + /// does not contain an equivalent flag for <see cref="FeatureAttributes.UIDisallowAbsent"/>, the presence
291 + /// of this flag will be ignored.
292 + /// </p></remarks>
293 + public FeatureAttributes Attributes
294 + {
295 + get
296 + {
297 + FeatureAttributes attributes;
298 + uint titleBufSize = 0;
299 + uint descBufSize = 0;
300 + uint attr;
301 + uint ret = NativeMethods.MsiGetFeatureInfo(
302 + (int) this.session.Handle,
303 + this.name,
304 + out attr,
305 + null,
306 + ref titleBufSize,
307 + null,
308 + ref descBufSize);
309 +
310 + if (ret != 0)
311 + {
312 + throw InstallerException.ExceptionFromReturnCode(ret);
313 + }
314 +
315 + // Values for attributes that MsiGetFeatureInfo returns are
316 + // double the values in the Attributes column of the Feature Table.
317 + attributes = (FeatureAttributes) (attr >> 1);
318 +
319 + // MsiGetFeatureInfo MSDN documentation indicates
320 + // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 16
321 + // which is UIDisallowAbsent. MsiGetFeatureInfo isn't documented to
322 + // return an attribute for 'UIDisallowAbsent', so if UIDisallowAbsent
323 + // is set, change it to NoUnsupportedAdvertise which then maps correctly
324 + // to NOUNSUPPORTEDADVERTISE.
325 + if ((attributes & FeatureAttributes.UIDisallowAbsent) == FeatureAttributes.UIDisallowAbsent)
326 + {
327 + attributes &= ~FeatureAttributes.UIDisallowAbsent;
328 + attributes |= FeatureAttributes.NoUnsupportedAdvertise;
329 + }
330 +
331 + return attributes;
332 + }
333 +
334 + set
335 + {
336 + // MsiSetFeatureAttributes doesn't indicate UIDisallowAbsent is valid
337 + // so remove it.
338 + FeatureAttributes attributes = value;
339 + attributes &= ~FeatureAttributes.UIDisallowAbsent;
340 +
341 + // Values for attributes that MsiSetFeatureAttributes uses are
342 + // double the values in the Attributes column of the Feature Table.
343 + uint attr = ((uint) attributes) << 1;
344 +
345 + // MsiSetFeatureAttributes MSDN documentation indicates
346 + // NOUNSUPPORTEDADVERTISE is 32. Conversion above changes this to 64
347 + // which is undefined. Change this back to 32.
348 + uint noUnsupportedAdvertiseDbl = ((uint)FeatureAttributes.NoUnsupportedAdvertise) << 1;
349 + if ((attr & noUnsupportedAdvertiseDbl) == noUnsupportedAdvertiseDbl)
350 + {
351 + attr &= ~noUnsupportedAdvertiseDbl;
352 + attr |= (uint) FeatureAttributes.NoUnsupportedAdvertise;
353 + }
354 +
355 + uint ret = RemotableNativeMethods.MsiSetFeatureAttributes((int) this.session.Handle, this.name, attr);
356 +
357 + if (ret != (uint)NativeMethods.Error.SUCCESS)
358 + {
359 + if (ret == (uint)NativeMethods.Error.UNKNOWN_FEATURE)
360 + {
361 + throw InstallerException.ExceptionFromReturnCode(ret, this.name);
362 + }
363 + else
364 + {
365 + throw InstallerException.ExceptionFromReturnCode(ret);
366 + }
367 + }
368 + }
369 + }
370 +
371 + /// <summary>
372 + /// Gets the title of the feature.
373 + /// </summary>
374 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
375 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
376 + /// <remarks><p>
377 + /// Win32 MSI API:
378 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
379 + /// </p></remarks>
380 + public string Title
381 + {
382 + get
383 + {
384 + StringBuilder titleBuf = new StringBuilder(80);
385 + uint titleBufSize = (uint) titleBuf.Capacity;
386 + uint descBufSize = 0;
387 + uint attr;
388 + uint ret = NativeMethods.MsiGetFeatureInfo(
389 + (int) this.session.Handle,
390 + this.name,
391 + out attr,
392 + titleBuf,
393 + ref titleBufSize,
394 + null,
395 + ref descBufSize);
396 +
397 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
398 + {
399 + titleBuf.Capacity = (int) ++titleBufSize;
400 + ret = NativeMethods.MsiGetFeatureInfo(
401 + (int) this.session.Handle,
402 + this.name,
403 + out attr,
404 + titleBuf,
405 + ref titleBufSize,
406 + null,
407 + ref descBufSize);
408 + }
409 +
410 + if (ret != 0)
411 + {
412 + throw InstallerException.ExceptionFromReturnCode(ret);
413 + }
414 +
415 + return titleBuf.ToString();
416 + }
417 + }
418 +
419 + /// <summary>
420 + /// Gets the description of the feature.
421 + /// </summary>
422 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
423 + /// <exception cref="ArgumentException">an unknown feature was requested</exception>
424 + /// <remarks><p>
425 + /// Win32 MSI API:
426 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureinfo.asp">MsiGetFeatureInfo</a>
427 + /// </p></remarks>
428 + public string Description
429 + {
430 + get
431 + {
432 + StringBuilder descBuf = new StringBuilder(256);
433 + uint titleBufSize = 0;
434 + uint descBufSize = (uint) descBuf.Capacity;
435 + uint attr;
436 + uint ret = NativeMethods.MsiGetFeatureInfo(
437 + (int) this.session.Handle,
438 + this.name,
439 + out attr,
440 + null,
441 + ref titleBufSize,
442 + descBuf,
443 + ref descBufSize);
444 +
445 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
446 + {
447 + descBuf.Capacity = (int) ++descBufSize;
448 + ret = NativeMethods.MsiGetFeatureInfo(
449 + (int) this.session.Handle,
450 + this.name,
451 + out attr,
452 + null,
453 + ref titleBufSize,
454 + descBuf,
455 + ref descBufSize);
456 + }
457 +
458 + if (ret != 0)
459 + {
460 + throw InstallerException.ExceptionFromReturnCode(ret);
461 + }
462 +
463 + return descBuf.ToString();
464 + }
465 + }
466 +
467 + /// <summary>
468 + /// Calculates the disk space required by the feature and its selected children and parent features.
469 + /// </summary>
470 + /// <param name="includeParents">If true, the parent features are included in the cost.</param>
471 + /// <param name="includeChildren">If true, the child features are included in the cost.</param>
472 + /// <param name="installState">Specifies the installation state.</param>
473 + /// <returns>The disk space requirement in bytes.</returns>
474 + /// <remarks><p>
475 + /// Win32 MSI API:
476 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeaturecost.asp">MsiGetFeatureCost</a>
477 + /// </p></remarks>
478 + public long GetCost(bool includeParents, bool includeChildren, InstallState installState)
479 + {
480 + const int MSICOSTTREE_CHILDREN = 1;
481 + const int MSICOSTTREE_PARENTS = 2;
482 +
483 + int cost;
484 + uint ret = RemotableNativeMethods.MsiGetFeatureCost(
485 + (int) this.session.Handle,
486 + this.name,
487 + (includeParents ? MSICOSTTREE_PARENTS : 0) | (includeChildren ? MSICOSTTREE_CHILDREN : 0),
488 + (int) installState,
489 + out cost);
490 + if (ret != 0)
491 + {
492 + throw InstallerException.ExceptionFromReturnCode(ret);
493 + }
494 + return cost * 512L;
495 + }
496 + }
497 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/FeatureInstallation.cs new
+174
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + /// <summary>
11 + /// Represents an instance of a feature of an installed product.
12 + /// </summary>
13 + public class FeatureInstallation : InstallationPart
14 + {
15 + /// <summary>
16 + /// Creates a new FeatureInstallation instance for a feature of a product.
17 + /// </summary>
18 + /// <param name="featureName">feature name</param>
19 + /// <param name="productCode">ProductCode GUID</param>
20 + public FeatureInstallation(string featureName, string productCode)
21 + : base(featureName, productCode)
22 + {
23 + if (String.IsNullOrEmpty(featureName))
24 + {
25 + throw new ArgumentNullException("featureName");
26 + }
27 + }
28 +
29 + /// <summary>
30 + /// Gets the name of the feature.
31 + /// </summary>
32 + public string FeatureName
33 + {
34 + get
35 + {
36 + return this.Id;
37 + }
38 + }
39 +
40 + /// <summary>
41 + /// Gets the installed state of the feature.
42 + /// </summary>
43 + /// <remarks><p>
44 + /// Win32 MSI API:
45 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>
46 + /// </p></remarks>
47 + public override InstallState State
48 + {
49 + get
50 + {
51 + int installState = NativeMethods.MsiQueryFeatureState(
52 + this.ProductCode, this.FeatureName);
53 + return (InstallState) installState;
54 + }
55 + }
56 +
57 + /// <summary>
58 + /// Gets the parent of the feature, or null if the feature has no parent (it is a root feature).
59 + /// </summary>
60 + /// <remarks>
61 + /// Invocation of this property may be slightly costly for products with many features,
62 + /// because it involves an enumeration of all the features in the product.
63 + /// </remarks>
64 + public FeatureInstallation Parent
65 + {
66 + get
67 + {
68 + StringBuilder featureBuf = new StringBuilder(256);
69 + StringBuilder parentBuf = new StringBuilder(256);
70 + for (uint i = 0; ; i++)
71 + {
72 + uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, featureBuf, parentBuf);
73 +
74 + if (ret != 0)
75 + {
76 + break;
77 + }
78 +
79 + if (featureBuf.ToString() == this.FeatureName)
80 + {
81 + if (parentBuf.Length > 0)
82 + {
83 + return new FeatureInstallation(parentBuf.ToString(), this.ProductCode);
84 + }
85 + else
86 + {
87 + return null;
88 + }
89 + }
90 + }
91 +
92 + return null;
93 + }
94 + }
95 +
96 + /// <summary>
97 + /// Gets the usage metrics for the feature.
98 + /// </summary>
99 + /// <remarks><p>
100 + /// If no usage metrics are recorded, the <see cref="UsageData.UseCount" /> value is 0.
101 + /// </p><p>
102 + /// Win32 MSI API:
103 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfeatureusage.asp">MsiGetFeatureUsage</a>
104 + /// </p></remarks>
105 + public FeatureInstallation.UsageData Usage
106 + {
107 + get
108 + {
109 + uint useCount;
110 + ushort useDate;
111 + uint ret = NativeMethods.MsiGetFeatureUsage(
112 + this.ProductCode, this.FeatureName, out useCount, out useDate);
113 + if (ret != 0)
114 + {
115 + throw InstallerException.ExceptionFromReturnCode(ret);
116 + }
117 +
118 + DateTime lastUsedDate;
119 + if (useCount == 0)
120 + {
121 + lastUsedDate = DateTime.MinValue;
122 + }
123 + else
124 + {
125 + lastUsedDate = new DateTime(
126 + 1980 + (useDate >> 9),
127 + (useDate & 0x01FF) >> 5,
128 + (useDate & 0x001F));
129 + }
130 +
131 + return new UsageData((int) useCount, lastUsedDate);
132 + }
133 + }
134 +
135 + /// <summary>
136 + /// Holds data about the usage of a feature.
137 + /// </summary>
138 + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
139 + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
140 + public struct UsageData
141 + {
142 + private int useCount;
143 + private DateTime lastUsedDate;
144 +
145 + internal UsageData(int useCount, DateTime lastUsedDate)
146 + {
147 + this.useCount = useCount;
148 + this.lastUsedDate = lastUsedDate;
149 + }
150 +
151 + /// <summary>
152 + /// Gets count of the number of times the feature has been used.
153 + /// </summary>
154 + public int UseCount
155 + {
156 + get
157 + {
158 + return this.useCount;
159 + }
160 + }
161 +
162 + /// <summary>
163 + /// Gets the date the feature was last used.
164 + /// </summary>
165 + public DateTime LastUsedDate
166 + {
167 + get
168 + {
169 + return this.lastUsedDate;
170 + }
171 + }
172 + }
173 + }
174 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Handle.cs new
+154
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.ComponentModel;
7 + using System.Diagnostics.CodeAnalysis;
8 +
9 + /// <summary>
10 + /// Base class for Windows Installer handle types (Database, View, Record, SummaryInfo).
11 + /// </summary>
12 + /// <remarks><p>
13 + /// These classes implement the <see cref="IDisposable"/> interface, because they
14 + /// hold unmanaged resources (MSI handles) that should be properly disposed
15 + /// when no longer needed.
16 + /// </p></remarks>
17 + public abstract class InstallerHandle : MarshalByRefObject, IDisposable
18 + {
19 + private NativeMethods.MsiHandle handle;
20 +
21 + /// <summary>
22 + /// Constructs a handle object from a native integer handle.
23 + /// </summary>
24 + /// <param name="handle">Native integer handle.</param>
25 + /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
26 + protected InstallerHandle(IntPtr handle, bool ownsHandle)
27 + {
28 + if (handle == IntPtr.Zero)
29 + {
30 + throw new InvalidHandleException();
31 + }
32 +
33 + this.handle = new NativeMethods.MsiHandle(handle, ownsHandle);
34 + }
35 +
36 + /// <summary>
37 + /// Gets the native integer handle.
38 + /// </summary>
39 + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
40 + public IntPtr Handle
41 + {
42 + get
43 + {
44 + if (this.IsClosed)
45 + {
46 + throw new InvalidHandleException();
47 + }
48 + return this.handle;
49 + }
50 + }
51 +
52 + /// <summary>
53 + /// Checks if the handle is closed. When closed, method calls on the handle object may throw an <see cref="InvalidHandleException"/>.
54 + /// </summary>
55 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
56 + public bool IsClosed
57 + {
58 + get
59 + {
60 + return this.handle.IsClosed;
61 + }
62 + }
63 +
64 + /// <summary>
65 + /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
66 + /// </summary>
67 + /// <remarks><p>
68 + /// The finalizer of this class will NOT close the handle if it is still open,
69 + /// because finalization can run on a separate thread from the application,
70 + /// resulting in potential problems if handles are closed from that thread.
71 + /// It is best that the handle be closed manually as soon as it is no longer needed,
72 + /// as leaving lots of unused handles open can degrade performance.
73 + /// </p><p>
74 + /// Win32 MSI API:
75 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a>
76 + /// </p></remarks>
77 + /// <seealso cref="Close"/>
78 + public void Dispose()
79 + {
80 + this.Dispose(true);
81 + GC.SuppressFinalize(this);
82 + }
83 +
84 + /// <summary>
85 + /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
86 + /// </summary>
87 + /// <remarks><p>
88 + /// The finalizer of this class will NOT close the handle if it is still open,
89 + /// because finalization can run on a separate thread from the application,
90 + /// resulting in potential problems if handles are closed from that thread.
91 + /// It is best that the handle be closed manually as soon as it is no longer needed,
92 + /// as leaving lots of unused handles open can degrade performance.
93 + /// </p><p>
94 + /// This method is merely an alias for the <see cref="Dispose()"/> method.
95 + /// </p><p>
96 + /// Win32 MSI API:
97 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiclosehandle.asp">MsiCloseHandle</a>
98 + /// </p></remarks>
99 + public void Close()
100 + {
101 + this.Dispose();
102 + }
103 +
104 + /// <summary>
105 + /// Tests whether this handle object is equal to another handle object. Two handle objects are equal
106 + /// if their types are the same and their native integer handles are the same.
107 + /// </summary>
108 + /// <param name="obj">The handle object to compare with the current handle object.</param>
109 + /// <returns>true if the specified handle object is equal to the current handle object; otherwise false</returns>
110 + public override bool Equals(object obj)
111 + {
112 + return (obj != null && this.GetType() == obj.GetType() &&
113 + this.Handle == ((InstallerHandle) obj).Handle);
114 + }
115 +
116 + /// <summary>
117 + /// Gets a hash value for the handle object.
118 + /// </summary>
119 + /// <returns>A hash code for the handle object.</returns>
120 + /// <remarks><p>
121 + /// The hash code is derived from the native integer handle.
122 + /// </p></remarks>
123 + public override int GetHashCode()
124 + {
125 + return this.Handle.GetHashCode();
126 + }
127 +
128 + /// <summary>
129 + /// Gets an object that can be used internally for safe syncronization.
130 + /// </summary>
131 + internal object Sync
132 + {
133 + get
134 + {
135 + return this.handle;
136 + }
137 + }
138 +
139 + /// <summary>
140 + /// Closes the handle. After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
141 + /// </summary>
142 + /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
143 + /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
144 + /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
145 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
146 + protected virtual void Dispose(bool disposing)
147 + {
148 + if (disposing)
149 + {
150 + this.handle.Dispose();
151 + }
152 + }
153 + }
154 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/IEmbeddedUI.cs new
+67
@@ -0,0 +1,67 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System.Diagnostics.CodeAnalysis;
6 +
7 + /// <summary>
8 + /// [MSI 4.5] Interface for an embedded external user interface for an installation.
9 + /// </summary>
10 + /// <remarks>
11 + /// Classes which implement this interface must have a public constructor that takes no parameters.
12 + /// </remarks>
13 + public interface IEmbeddedUI
14 + {
15 + /// <summary>
16 + /// Initializes the embedded UI.
17 + /// </summary>
18 + /// <param name="session">Handle to the installer which can be used to get and set properties.
19 + /// The handle is only valid for the duration of this method call.</param>
20 + /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
21 + /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
22 + /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
23 + /// <returns>True if the embedded UI was successfully initialized; false if the installation
24 + /// should continue without the embedded UI.</returns>
25 + /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
26 + /// <exception cref="InstallerException">The embedded UI failed to initialize and
27 + /// causes the installation to fail.</exception>
28 + /// <remarks><p>
29 + /// Win32 MSI API:
30 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/initializeembeddedui.asp">InitializeEmbeddedUI</a>
31 + /// </p></remarks>
32 + [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
33 + bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel);
34 +
35 + /// <summary>
36 + /// Processes information and progress messages sent to the user interface.
37 + /// </summary>
38 + /// <param name="messageType">Message type.</param>
39 + /// <param name="messageRecord">Record that contains message data.</param>
40 + /// <param name="buttons">Message buttons.</param>
41 + /// <param name="icon">Message box icon.</param>
42 + /// <param name="defaultButton">Message box default button.</param>
43 + /// <returns>Result of processing the message.</returns>
44 + /// <remarks><p>
45 + /// Win32 MSI API:
46 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/embeddeduihandler.asp">EmbeddedUIHandler</a>
47 + /// </p></remarks>
48 + MessageResult ProcessMessage(
49 + InstallMessage messageType,
50 + Record messageRecord,
51 + MessageButtons buttons,
52 + MessageIcon icon,
53 + MessageDefaultButton defaultButton);
54 +
55 + /// <summary>
56 + /// Shuts down the embedded UI at the end of the installation.
57 + /// </summary>
58 + /// <remarks>
59 + /// If the installation was canceled during initialization, this method will not be called.
60 + /// If the installation was canceled or failed at any later point, this method will be called at the end.
61 + /// <p>
62 + /// Win32 MSI API:
63 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/shutdownembeddedui.asp">ShutdownEmbeddedUI</a>
64 + /// </p></remarks>
65 + void Shutdown();
66 + }
67 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/InstallCost.cs new
+67
@@ -0,0 +1,67 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System.Diagnostics.CodeAnalysis;
6 +
7 + /// <summary>
8 + /// Represents a per-drive disk space cost for an installation.
9 + /// </summary>
10 + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
11 + public struct InstallCost
12 + {
13 + private string driveName;
14 + private long cost;
15 + private long tempCost;
16 +
17 + /// <summary>
18 + /// Creates a new InstallCost object.
19 + /// </summary>
20 + /// <param name="driveName">name of the drive this cost data applies to</param>
21 + /// <param name="cost">installation cost on this drive, as a number of bytes</param>
22 + /// <param name="tempCost">temporary disk space required on this drive, as a number of bytes</param>
23 + internal InstallCost(string driveName, long cost, long tempCost)
24 + {
25 + this.driveName = driveName;
26 + this.cost = cost;
27 + this.tempCost = tempCost;
28 + }
29 +
30 + /// <summary>
31 + /// The name of the drive this cost data applies to.
32 + /// </summary>
33 + public string DriveName
34 + {
35 + get
36 + {
37 + return this.driveName;
38 + }
39 + }
40 +
41 + /// <summary>
42 + /// The installation cost on this drive, as a number of bytes.
43 + /// </summary>
44 + public long Cost
45 + {
46 + get
47 + {
48 + return this.cost;
49 + }
50 + }
51 +
52 + /// <summary>
53 + /// The temporary disk space required on this drive, as a number of bytes.
54 + /// </summary>
55 + /// <remarks><p>
56 + /// This temporary space requirement is space needed only for the duration
57 + /// of the installation, over the final footprint on disk.
58 + /// </p></remarks>
59 + public long TempCost
60 + {
61 + get
62 + {
63 + return this.tempCost;
64 + }
65 + }
66 + }
67 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Installation.cs new
+100
@@ -0,0 +1,100 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Globalization;
8 +
9 + /// <summary>
10 + /// Subclasses of this abstract class represent a unique instance of a
11 + /// registered product or patch installation.
12 + /// </summary>
13 + public abstract class Installation
14 + {
15 + private string installationCode;
16 + private string userSid;
17 + private UserContexts context;
18 + private SourceList sourceList;
19 +
20 + internal Installation(string installationCode, string userSid, UserContexts context)
21 + {
22 + if (context == UserContexts.Machine)
23 + {
24 + userSid = null;
25 + }
26 + this.installationCode = installationCode;
27 + this.userSid = userSid;
28 + this.context = context;
29 + }
30 +
31 + /// <summary>
32 + /// Gets the user security identifier (SID) under which this product or patch
33 + /// installation is available.
34 + /// </summary>
35 + public string UserSid
36 + {
37 + get
38 + {
39 + return this.userSid;
40 + }
41 + }
42 +
43 + /// <summary>
44 + /// Gets the user context of this product or patch installation.
45 + /// </summary>
46 + public UserContexts Context
47 + {
48 + get
49 + {
50 + return this.context;
51 + }
52 + }
53 +
54 + /// <summary>
55 + /// Gets the source list of this product or patch installation.
56 + /// </summary>
57 + public virtual SourceList SourceList
58 + {
59 + get
60 + {
61 + if (this.sourceList == null)
62 + {
63 + this.sourceList = new SourceList(this);
64 + }
65 + return this.sourceList;
66 + }
67 + }
68 +
69 + /// <summary>
70 + /// Gets a value indicating whether this product or patch is installed on the current system.
71 + /// </summary>
72 + public abstract bool IsInstalled
73 + {
74 + get;
75 + }
76 +
77 + internal string InstallationCode
78 + {
79 + get
80 + {
81 + return this.installationCode;
82 + }
83 + }
84 +
85 + internal abstract int InstallationType
86 + {
87 + get;
88 + }
89 +
90 + /// <summary>
91 + /// Gets a property about the product or patch installation.
92 + /// </summary>
93 + /// <param name="propertyName">Name of the property being retrieved.</param>
94 + /// <returns></returns>
95 + public abstract string this[string propertyName]
96 + {
97 + get;
98 + }
99 + }
100 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/InstallationPart.cs new
+82
@@ -0,0 +1,82 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + /// <summary>
6 + /// Subclasses of this abstract class represent an instance
7 + /// of a registered feature or component.
8 + /// </summary>
9 + public abstract class InstallationPart
10 + {
11 + private string id;
12 + private string productCode;
13 + private string userSid;
14 + private UserContexts context;
15 +
16 + internal InstallationPart(string id, string productCode)
17 + : this(id, productCode, null, UserContexts.None)
18 + {
19 + }
20 +
21 + internal InstallationPart(string id, string productCode, string userSid, UserContexts context)
22 + {
23 + this.id = id;
24 + this.productCode = productCode;
25 + this.userSid = userSid;
26 + this.context = context;
27 + }
28 +
29 + internal string Id
30 + {
31 + get
32 + {
33 + return this.id;
34 + }
35 + }
36 +
37 + internal string ProductCode
38 + {
39 + get
40 + {
41 + return this.productCode;
42 + }
43 + }
44 +
45 + internal string UserSid
46 + {
47 + get
48 + {
49 + return this.userSid;
50 + }
51 + }
52 +
53 + internal UserContexts Context
54 + {
55 + get
56 + {
57 + return this.context;
58 + }
59 + }
60 +
61 + /// <summary>
62 + /// Gets the product that this item is a part of.
63 + /// </summary>
64 + public ProductInstallation Product
65 + {
66 + get
67 + {
68 + return this.productCode != null ?
69 + new ProductInstallation(this.productCode, userSid, context) : null;
70 + }
71 + }
72 +
73 + /// <summary>
74 + /// Gets the current installation state of the item.
75 + /// </summary>
76 + public abstract InstallState State
77 + {
78 + get;
79 + }
80 + }
81 +
82 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Installer.cs new
+890
@@ -0,0 +1,890 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Resources;
9 + using System.Reflection;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Runtime.InteropServices;
13 + using System.Diagnostics.CodeAnalysis;
14 +
15 +/// <summary>
16 +/// Receives an exception from
17 +/// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
18 +/// indicating the reason a particular patch is not applicable to a product.
19 +/// </summary>
20 +/// <param name="patch">MSP file path, XML file path, or XML blob that was passed to
21 +/// <see cref="Installer.DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/></param>
22 +/// <param name="exception">exception indicating the reason the patch is not applicable</param>
23 +/// <remarks><p>
24 +/// If <paramref name="exception"/> is an <see cref="InstallerException"/> or subclass, then
25 +/// its <see cref="InstallerException.ErrorCode"/> and <see cref="InstallerException.Message"/>
26 +/// properties will indicate a more specific reason the patch was not applicable.
27 +/// </p><p>
28 +/// The <paramref name="exception"/> could also be a FileNotFoundException if the
29 +/// patch string was a file path.
30 +/// </p></remarks>
31 +public delegate void InapplicablePatchHandler(string patch, Exception exception);
32 +
33 +/// <summary>
34 +/// Provides static methods for installing and configuring products and patches.
35 +/// </summary>
36 +public static partial class Installer
37 +{
38 + private static bool rebootRequired;
39 + private static bool rebootInitiated;
40 + private static ResourceManager errorResources;
41 +
42 + /// <summary>
43 + /// Indicates whether a system reboot is required after running an installation or configuration operation.
44 + /// </summary>
45 + public static bool RebootRequired
46 + {
47 + get
48 + {
49 + return Installer.rebootRequired;
50 + }
51 + }
52 +
53 + /// <summary>
54 + /// Indicates whether a system reboot has been initiated after running an installation or configuration operation.
55 + /// </summary>
56 + public static bool RebootInitiated
57 + {
58 + get
59 + {
60 + return Installer.rebootInitiated;
61 + }
62 + }
63 +
64 + /// <summary>
65 + /// Enables the installer's internal user interface. Then this user interface is used
66 + /// for all subsequent calls to user-interface-generating installer functions in this process.
67 + /// </summary>
68 + /// <param name="uiOptions">Specifies the level of complexity of the user interface</param>
69 + /// <param name="windowHandle">Handle to a window, which becomes the owner of any user interface created.
70 + /// A pointer to the previous owner of the user interface is returned.</param>
71 + /// <returns>The previous user interface level</returns>
72 + /// <remarks><p>
73 + /// Win32 MSI API:
74 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a>
75 + /// </p></remarks>
76 + [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
77 + public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions, ref IntPtr windowHandle)
78 + {
79 + return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, ref windowHandle);
80 + }
81 +
82 + /// <summary>
83 + /// Enables the installer's internal user interface. Then this user interface is used
84 + /// for all subsequent calls to user-interface-generating installer functions in this process.
85 + /// The owner of the user interface does not change.
86 + /// </summary>
87 + /// <param name="uiOptions">Specifies the level of complexity of the user interface</param>
88 + /// <returns>The previous user interface level</returns>
89 + /// <remarks><p>
90 + /// Win32 MSI API:
91 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinternalui.asp">MsiSetInternalUI</a>
92 + /// </p></remarks>
93 + public static InstallUIOptions SetInternalUI(InstallUIOptions uiOptions)
94 + {
95 + return (InstallUIOptions) NativeMethods.MsiSetInternalUI((uint) uiOptions, IntPtr.Zero);
96 + }
97 +
98 + /// <summary>
99 + /// Enables logging of the selected message type for all subsequent install sessions in
100 + /// the current process space.
101 + /// </summary>
102 + /// <param name="logModes">One or more mode flags specifying the type of messages to log</param>
103 + /// <param name="logFile">Full path to the log file. A null path disables logging,
104 + /// in which case the logModes paraneter is ignored.</param>
105 + /// <exception cref="ArgumentException">an invalid log mode was specified</exception>
106 + /// <remarks>This method takes effect on any new installation processes. Calling this
107 + /// method from within a custom action will not start logging for that installation.</remarks>
108 + public static void EnableLog(InstallLogModes logModes, string logFile)
109 + {
110 + Installer.EnableLog(logModes, logFile, false, true);
111 + }
112 +
113 + /// <summary>
114 + /// Enables logging of the selected message type for all subsequent install sessions in
115 + /// the current process space.
116 + /// </summary>
117 + /// <param name="logModes">One or more mode flags specifying the type of messages to log</param>
118 + /// <param name="logFile">Full path to the log file. A null path disables logging,
119 + /// in which case the logModes paraneter is ignored.</param>
120 + /// <param name="append">If true, the log lines will be appended to any existing file content.
121 + /// If false, the log file will be truncated if it exists. The default is false.</param>
122 + /// <param name="flushEveryLine">If true, the log will be flushed after every line.
123 + /// If false, the log will be flushed every 20 lines. The default is true.</param>
124 + /// <exception cref="ArgumentException">an invalid log mode was specified</exception>
125 + /// <remarks><p>
126 + /// This method takes effect on any new installation processes. Calling this
127 + /// method from within a custom action will not start logging for that installation.
128 + /// </p><p>
129 + /// Win32 MSI API:
130 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienablelog.asp">MsiEnableLog</a>
131 + /// </p></remarks>
132 + public static void EnableLog(InstallLogModes logModes, string logFile, bool append, bool flushEveryLine)
133 + {
134 + uint ret = NativeMethods.MsiEnableLog((uint) logModes, logFile, (append ? (uint) 1 : 0) + (flushEveryLine ? (uint) 2 : 0));
135 + if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
136 + {
137 + throw InstallerException.ExceptionFromReturnCode(ret);
138 + }
139 + }
140 +
141 + /// <summary>
142 + /// increments the usage count for a particular feature and returns the installation state for
143 + /// that feature. This method should be used to indicate an application's intent to use a feature.
144 + /// </summary>
145 + /// <param name="productCode">The product code of the product.</param>
146 + /// <param name="feature">The feature to be used.</param>
147 + /// <param name="installMode">Must have the value <see cref="InstallMode.NoDetection"/>.</param>
148 + /// <returns>The installed state of the feature.</returns>
149 + /// <remarks><p>
150 + /// The UseFeature method should only be used on features known to be published. The application
151 + /// should determine the status of the feature by calling either the FeatureState method or
152 + /// Features method.
153 + /// </p><p>
154 + /// Win32 MSI APIs:
155 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeature.asp">MsiUseFeature</a>,
156 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiusefeatureex.asp">MsiUseFeatureEx</a>
157 + /// </p></remarks>
158 + public static InstallState UseFeature(string productCode, string feature, InstallMode installMode)
159 + {
160 + int installState = NativeMethods.MsiUseFeatureEx(productCode, feature, unchecked ((uint) installMode), 0);
161 + return (InstallState) installState;
162 + }
163 +
164 + /// <summary>
165 + /// Opens an installer package for use with functions that access the product database and install engine,
166 + /// returning an Session object.
167 + /// </summary>
168 + /// <param name="packagePath">Path to the package</param>
169 + /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the
170 + /// computer state and that is incapable of changing the current computer state. A value of false yields
171 + /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current
172 + /// machine state.</param>
173 + /// <returns>A Session object allowing access to the product database and install engine</returns>
174 + /// <exception cref="InstallerException">The product could not be opened</exception>
175 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
176 + /// <remarks><p>
177 + /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a
178 + /// custom action because the active installation is the only session allowed.
179 + /// </p><p>
180 + /// A "safe" Session object ignores the current computer state when opening the package and prevents
181 + /// changes to the current computer state.
182 + /// </p><p>
183 + /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
184 + /// It is best that the handle be closed manually as soon as it is no longer
185 + /// needed, as leaving lots of unused handles open can degrade performance.
186 + /// </p><p>
187 + /// Win32 MSI APIs:
188 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>,
189 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a>
190 + /// </p></remarks>
191 + public static Session OpenPackage(string packagePath, bool ignoreMachineState)
192 + {
193 + int sessionHandle;
194 + uint ret = NativeMethods.MsiOpenPackageEx(packagePath, ignoreMachineState ? (uint) 1 : 0, out sessionHandle);
195 + if (ret != 0)
196 + {
197 + throw InstallerException.ExceptionFromReturnCode(ret);
198 + }
199 + return new Session((IntPtr) sessionHandle, true);
200 + }
201 +
202 + /// <summary>
203 + /// Opens an installer package for use with functions that access the product database and install engine,
204 + /// returning an Session object.
205 + /// </summary>
206 + /// <param name="database">Database used to create the session</param>
207 + /// <param name="ignoreMachineState">Specifies whether or not the create a Session object that ignores the
208 + /// computer state and that is incapable of changing the current computer state. A value of false yields
209 + /// the normal behavior. A value of true creates a "safe" Session object that cannot change of the current
210 + /// machine state.</param>
211 + /// <returns>A Session object allowing access to the product database and install engine</returns>
212 + /// <exception cref="InstallerException">The product could not be opened</exception>
213 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
214 + /// <remarks><p>
215 + /// Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a
216 + /// custom action because the active installation is the only session allowed.
217 + /// </p><p>
218 + /// A "safe" Session object ignores the current computer state when opening the package and prevents
219 + /// changes to the current computer state.
220 + /// </p><p>
221 + /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
222 + /// It is best that the handle be closed manually as soon as it is no longer
223 + /// needed, as leaving lots of unused handles open can degrade performance.
224 + /// </p><p>
225 + /// Win32 MSI APIs:
226 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackage.asp">MsiOpenPackage</a>,
227 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenpackageex.asp">MsiOpenPackageEx</a>
228 + /// </p></remarks>
229 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
230 + public static Session OpenPackage(Database database, bool ignoreMachineState)
231 + {
232 + if (database == null)
233 + {
234 + throw new ArgumentNullException("database");
235 + }
236 +
237 + return Installer.OpenPackage(
238 + String.Format(CultureInfo.InvariantCulture, "#{0}", database.Handle),
239 + ignoreMachineState);
240 + }
241 +
242 + /// <summary>
243 + /// Opens an installer package for an installed product using the product code.
244 + /// </summary>
245 + /// <param name="productCode">Product code of the installed product</param>
246 + /// <returns>A Session object allowing access to the product database and install engine,
247 + /// or null if the specified product is not installed.</returns>
248 + /// <exception cref="ArgumentException">An unknown product was requested</exception>
249 + /// <exception cref="InstallerException">The product could not be opened</exception>
250 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
251 + /// <remarks><p>
252 + /// Note that only one Session object can be opened by a single process. OpenProduct cannot be
253 + /// used in a custom action because the active installation is the only session allowed.
254 + /// </p><p>
255 + /// The Session object should be <see cref="InstallerHandle.Close"/>d after use.
256 + /// It is best that the handle be closed manually as soon as it is no longer
257 + /// needed, as leaving lots of unused handles open can degrade performance.
258 + /// </p><p>
259 + /// Win32 MSI API:
260 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiopenproduct.asp">MsiOpenProduct</a>
261 + /// </p></remarks>
262 + public static Session OpenProduct(string productCode)
263 + {
264 + int sessionHandle;
265 + uint ret = NativeMethods.MsiOpenProduct(productCode, out sessionHandle);
266 + if (ret != 0)
267 + {
268 + if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT)
269 + {
270 + return null;
271 + }
272 + else
273 + {
274 + throw InstallerException.ExceptionFromReturnCode(ret);
275 + }
276 + }
277 + return new Session((IntPtr) sessionHandle, true);
278 + }
279 +
280 + /// <summary>
281 + /// Gets the full component path, performing any necessary installation. This method prompts for source if
282 + /// necessary and increments the usage count for the feature.
283 + /// </summary>
284 + /// <param name="product">Product code for the product that contains the feature with the necessary component</param>
285 + /// <param name="feature">Feature ID of the feature with the necessary component</param>
286 + /// <param name="component">Component code of the necessary component</param>
287 + /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
288 + /// <returns>Path to the component</returns>
289 + /// <remarks><p>
290 + /// Win32 MSI API:
291 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidecomponent.asp">MsiProvideComponent</a>
292 + /// </p></remarks>
293 + public static string ProvideComponent(string product, string feature, string component, InstallMode installMode)
294 + {
295 + StringBuilder pathBuf = new StringBuilder(512);
296 + uint pathBufSize = (uint) pathBuf.Capacity;
297 + uint ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize);
298 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
299 + {
300 + pathBuf.Capacity = (int) ++pathBufSize;
301 + ret = NativeMethods.MsiProvideComponent(product, feature, component, unchecked((uint)installMode), pathBuf, ref pathBufSize);
302 + }
303 +
304 + if (ret != 0)
305 + {
306 + throw InstallerException.ExceptionFromReturnCode(ret);
307 + }
308 + return pathBuf.ToString();
309 + }
310 +
311 + /// <summary>
312 + /// Gets the full component path for a qualified component that is published by a product and
313 + /// performs any necessary installation. This method prompts for source if necessary and increments
314 + /// the usage count for the feature.
315 + /// </summary>
316 + /// <param name="component">Specifies the component ID for the requested component. This may not be the
317 + /// GUID for the component itself but rather a server that provides the correct functionality, as in the
318 + /// ComponentId column of the PublishComponent table.</param>
319 + /// <param name="qualifier">Specifies a qualifier into a list of advertising components (from PublishComponent Table).</param>
320 + /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
321 + /// <param name="product">Optional; specifies the product to match that has published the qualified component.</param>
322 + /// <returns>Path to the component</returns>
323 + /// <remarks><p>
324 + /// Win32 MSI APIs:
325 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponent.asp">MsiProvideQualifiedComponent</a>
326 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovidequalifiedcomponentex.asp">MsiProvideQualifiedComponentEx</a>
327 + /// </p></remarks>
328 + public static string ProvideQualifiedComponent(string component, string qualifier, InstallMode installMode, string product)
329 + {
330 + StringBuilder pathBuf = new StringBuilder(512);
331 + uint pathBufSize = (uint) pathBuf.Capacity;
332 + uint ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize);
333 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
334 + {
335 + pathBuf.Capacity = (int) ++pathBufSize;
336 + ret = NativeMethods.MsiProvideQualifiedComponentEx(component, qualifier, unchecked((uint)installMode), product, 0, 0, pathBuf, ref pathBufSize);
337 + }
338 +
339 + if (ret != 0)
340 + {
341 + throw InstallerException.ExceptionFromReturnCode(ret);
342 + }
343 + return pathBuf.ToString();
344 + }
345 +
346 + /// <summary>
347 + /// Gets the full path to a Windows Installer component containing an assembly. This method prompts for a source and
348 + /// increments the usage count for the feature.
349 + /// </summary>
350 + /// <param name="assemblyName">Assembly name</param>
351 + /// <param name="appContext">Set to null for global assemblies. For private assemblies, set to the full path of the
352 + /// application configuration file (.cfg file) or executable file (.exe) of the application to which the assembly
353 + /// has been made private.</param>
354 + /// <param name="installMode">Installation mode; this can also include bits from <see cref="ReinstallModes"/></param>
355 + /// <param name="isWin32Assembly">True if this is a Win32 assembly, false if it is a .NET assembly</param>
356 + /// <returns>Path to the assembly</returns>
357 + /// <remarks><p>
358 + /// Win32 MSI API:
359 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprovideassembly.asp">MsiProvideAssembly</a>
360 + /// </p></remarks>
361 + public static string ProvideAssembly(string assemblyName, string appContext, InstallMode installMode, bool isWin32Assembly)
362 + {
363 + StringBuilder pathBuf = new StringBuilder(512);
364 + uint pathBufSize = (uint) pathBuf.Capacity;
365 + uint ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize);
366 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
367 + {
368 + pathBuf.Capacity = (int) ++pathBufSize;
369 + ret = NativeMethods.MsiProvideAssembly(assemblyName, appContext, unchecked ((uint) installMode), (isWin32Assembly ? (uint) 1 : 0), pathBuf, ref pathBufSize);
370 + }
371 +
372 + if (ret != 0)
373 + {
374 + throw InstallerException.ExceptionFromReturnCode(ret);
375 + }
376 + return pathBuf.ToString();
377 + }
378 +
379 + /// <summary>
380 + /// Installs files that are unexpectedly missing.
381 + /// </summary>
382 + /// <param name="product">Product code for the product that owns the component to be installed</param>
383 + /// <param name="component">Component to be installed</param>
384 + /// <param name="installState">Specifies the way the component should be installed.</param>
385 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
386 + /// <remarks><p>
387 + /// Win32 MSI API:
388 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingcomponent.asp">MsiInstallMissingComponent</a>
389 + /// </p></remarks>
390 + public static void InstallMissingComponent(string product, string component, InstallState installState)
391 + {
392 + uint ret = NativeMethods.MsiInstallMissingComponent(product, component, (int) installState);
393 + if (ret != 0)
394 + {
395 + throw InstallerException.ExceptionFromReturnCode(ret);
396 + }
397 + }
398 +
399 + /// <summary>
400 + /// Installs files that are unexpectedly missing.
401 + /// </summary>
402 + /// <param name="product">Product code for the product that owns the file to be installed</param>
403 + /// <param name="file">File to be installed</param>
404 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
405 + /// <remarks><p>
406 + /// Win32 MSI API:
407 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallmissingfile.asp">MsiInstallMissingFile</a>
408 + /// </p></remarks>
409 + public static void InstallMissingFile(string product, string file)
410 + {
411 + uint ret = NativeMethods.MsiInstallMissingFile(product, file);
412 + if (ret != 0)
413 + {
414 + throw InstallerException.ExceptionFromReturnCode(ret);
415 + }
416 + }
417 +
418 + /// <summary>
419 + /// Reinstalls a feature.
420 + /// </summary>
421 + /// <param name="product">Product code for the product containing the feature to be reinstalled</param>
422 + /// <param name="feature">Feature to be reinstalled</param>
423 + /// <param name="reinstallModes">Reinstall modes</param>
424 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
425 + /// <remarks><p>
426 + /// Win32 MSI API:
427 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallfeature.asp">MsiReinstallFeature</a>
428 + /// </p></remarks>
429 + public static void ReinstallFeature(string product, string feature, ReinstallModes reinstallModes)
430 + {
431 + uint ret = NativeMethods.MsiReinstallFeature(product, feature, (uint) reinstallModes);
432 + if (ret != 0)
433 + {
434 + throw InstallerException.ExceptionFromReturnCode(ret);
435 + }
436 + }
437 +
438 + /// <summary>
439 + /// Reinstalls a product.
440 + /// </summary>
441 + /// <param name="product">Product code for the product to be reinstalled</param>
442 + /// <param name="reinstallModes">Reinstall modes</param>
443 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
444 + /// <remarks><p>
445 + /// Win32 MSI API:
446 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msireinstallproduct.asp">MsiReinstallProduct</a>
447 + /// </p></remarks>
448 + public static void ReinstallProduct(string product, ReinstallModes reinstallModes)
449 + {
450 + uint ret = NativeMethods.MsiReinstallProduct(product, (uint) reinstallModes);
451 + if (ret != 0)
452 + {
453 + throw InstallerException.ExceptionFromReturnCode(ret);
454 + }
455 + }
456 +
457 + /// <summary>
458 + /// Opens an installer package and initializes an install session.
459 + /// </summary>
460 + /// <param name="packagePath">path to the patch package</param>
461 + /// <param name="commandLine">command line property settings</param>
462 + /// <exception cref="InstallerException">There was an error installing the product</exception>
463 + /// <remarks><p>
464 + /// To completely remove a product, set REMOVE=ALL in <paramRef name="commandLine"/>.
465 + /// </p><p>
466 + /// This method displays the user interface with the current settings and
467 + /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/>
468 + /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the
469 + /// <see cref="EnableLog(InstallLogModes,string)"/> function.
470 + /// </p><p>
471 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
472 + /// tested after calling this method.
473 + /// </p><p>
474 + /// Win32 MSI API:
475 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiinstallproduct.asp">MsiInstallProduct</a>
476 + /// </p></remarks>
477 + public static void InstallProduct(string packagePath, string commandLine)
478 + {
479 + uint ret = NativeMethods.MsiInstallProduct(packagePath, commandLine);
480 + Installer.CheckInstallResult(ret);
481 + }
482 +
483 + /// <summary>
484 + /// Installs or uninstalls a product.
485 + /// </summary>
486 + /// <param name="productCode">Product code of the product to be configured.</param>
487 + /// <param name="installLevel">Specifies the default installation configuration of the
488 + /// product. The <paramref name="installLevel"/> parameter is ignored and all features
489 + /// are installed if the <paramref name="installState"/> parameter is set to any other
490 + /// value than <see cref="InstallState.Default"/>. This parameter must be either 0
491 + /// (install using authored feature levels), 65535 (install all features), or a value
492 + /// between 0 and 65535 to install a subset of available features. </param>
493 + /// <param name="installState">Specifies the installation state for the product.</param>
494 + /// <param name="commandLine">Specifies the command line property settings. This should
495 + /// be a list of the format Property=Setting Property=Setting.</param>
496 + /// <exception cref="InstallerException">There was an error configuring the product</exception>
497 + /// <remarks><p>
498 + /// This method displays the user interface with the current settings and
499 + /// log mode. You can change user interface settings with the <see cref="SetInternalUI(InstallUIOptions)"/>
500 + /// and <see cref="SetExternalUI(ExternalUIHandler,InstallLogModes)"/> functions. You can set the log mode with the
501 + /// <see cref="EnableLog(InstallLogModes,string)"/> function.
502 + /// </p><p>
503 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
504 + /// tested after calling this method.
505 + /// </p><p>
506 + /// Win32 MSI APIs:
507 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproduct.asp">MsiConfigureProduct</a>,
508 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigureproductex.asp">MsiConfigureProductEx</a>
509 + /// </p></remarks>
510 + public static void ConfigureProduct(string productCode, int installLevel, InstallState installState, string commandLine)
511 + {
512 + uint ret = NativeMethods.MsiConfigureProductEx(productCode, installLevel, (int) installState, commandLine);
513 + Installer.CheckInstallResult(ret);
514 + }
515 +
516 + /// <summary>
517 + /// Configures the installed state for a product feature.
518 + /// </summary>
519 + /// <param name="productCode">Product code of the product to be configured.</param>
520 + /// <param name="feature">Specifies the feature ID for the feature to be configured.</param>
521 + /// <param name="installState">Specifies the installation state for the feature.</param>
522 + /// <exception cref="InstallerException">There was an error configuring the feature</exception>
523 + /// <remarks><p>
524 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
525 + /// tested after calling this method.
526 + /// </p><p>
527 + /// Win32 MSI API:
528 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiconfigurefeature.asp">MsiConfigureFeature</a>
529 + /// </p></remarks>
530 + public static void ConfigureFeature(string productCode, string feature, InstallState installState)
531 + {
532 + uint ret = NativeMethods.MsiConfigureFeature(productCode, feature, (int) installState);
533 + Installer.CheckInstallResult(ret);
534 + }
535 +
536 + /// <summary>
537 + /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
538 + /// an installation and sets the PATCH property to the path of the patch package.
539 + /// </summary>
540 + /// <param name="patchPackage">path to the patch package</param>
541 + /// <param name="commandLine">optional command line property settings</param>
542 + /// <exception cref="InstallerException">There was an error applying the patch</exception>
543 + /// <remarks><p>
544 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
545 + /// tested after calling this method.
546 + /// </p><p>
547 + /// Win32 MSI API:
548 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a>
549 + /// </p></remarks>
550 + public static void ApplyPatch(string patchPackage, string commandLine)
551 + {
552 + Installer.ApplyPatch(patchPackage, null, InstallType.Default, commandLine);
553 + }
554 +
555 + /// <summary>
556 + /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
557 + /// an installation and sets the PATCH property to the path of the patch package.
558 + /// </summary>
559 + /// <param name="patchPackage">path to the patch package</param>
560 + /// <param name="installPackage">path to the product to be patched, if installType
561 + /// is set to <see cref="InstallType.NetworkImage"/></param>
562 + /// <param name="installType">type of installation to patch</param>
563 + /// <param name="commandLine">optional command line property settings</param>
564 + /// <exception cref="InstallerException">There was an error applying the patch</exception>
565 + /// <remarks><p>
566 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
567 + /// tested after calling this method.
568 + /// </p><p>
569 + /// Win32 MSI API:
570 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplypatch.asp">MsiApplyPatch</a>
571 + /// </p></remarks>
572 + public static void ApplyPatch(string patchPackage, string installPackage, InstallType installType, string commandLine)
573 + {
574 + uint ret = NativeMethods.MsiApplyPatch(patchPackage, installPackage, (int) installType, commandLine);
575 + Installer.CheckInstallResult(ret);
576 + }
577 +
578 + /// <summary>
579 + /// Removes one or more patches from a single product. To remove a patch from
580 + /// multiple products, RemovePatches must be called for each product.
581 + /// </summary>
582 + /// <param name="patches">List of patches to remove. Each patch can be specified by the GUID
583 + /// of the patch or the full path to the patch package.</param>
584 + /// <param name="productCode">The ProductCode (GUID) of the product from which the patches
585 + /// are removed. This parameter cannot be null.</param>
586 + /// <param name="commandLine">optional command line property settings</param>
587 + /// <exception cref="InstallerException">There was an error removing the patches</exception>
588 + /// <remarks><p>
589 + /// The <see cref="RebootRequired"/> and <see cref="RebootInitiated"/> properties should be
590 + /// tested after calling this method.
591 + /// </p><p>
592 + /// Win32 MSI API:
593 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiremovepatches.asp">MsiRemovePatches</a>
594 + /// </p></remarks>
595 + public static void RemovePatches(IList<string> patches, string productCode, string commandLine)
596 + {
597 + if (patches == null || patches.Count == 0)
598 + {
599 + throw new ArgumentNullException("patches");
600 + }
601 +
602 + if (productCode == null)
603 + {
604 + throw new ArgumentNullException("productCode");
605 + }
606 +
607 + StringBuilder patchList = new StringBuilder();
608 + foreach (string patch in patches)
609 + {
610 + if (patch != null)
611 + {
612 + if (patchList.Length != 0)
613 + {
614 + patchList.Append(';');
615 + }
616 +
617 + patchList.Append(patch);
618 + }
619 + }
620 +
621 + if (patchList.Length == 0)
622 + {
623 + throw new ArgumentNullException("patches");
624 + }
625 +
626 + uint ret = NativeMethods.MsiRemovePatches(patchList.ToString(), productCode, (int) InstallType.SingleInstance, commandLine);
627 + Installer.CheckInstallResult(ret);
628 + }
629 +
630 + /// <summary>
631 + /// Determines which patches apply to a specified product MSI and in what sequence.
632 + /// </summary>
633 + /// <param name="productPackage">Full path to an MSI file that is the target product
634 + /// for the set of patches.</param>
635 + /// <param name="patches">An array of strings specifying the patches to be checked. Each item
636 + /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param>
637 + /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the
638 + /// reason the patch is not applicable. This value may be left null if that information is not
639 + /// desired.</param>
640 + /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating
641 + /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns>
642 + /// <remarks><p>
643 + /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML,
644 + /// it is assumed to be an MSP file.
645 + /// </p><p>
646 + /// As this overload uses InstallContext.None, it does not consider the current state of
647 + /// the system.
648 + /// </p><p>
649 + /// Win32 MSI API:
650 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a>
651 + /// </p></remarks>
652 + public static IList<string> DetermineApplicablePatches(
653 + string productPackage,
654 + string[] patches,
655 + InapplicablePatchHandler errorHandler)
656 + {
657 + return DetermineApplicablePatches(productPackage, patches, errorHandler, null, UserContexts.None);
658 + }
659 +
660 + /// <summary>
661 + /// Determines which patches apply to a specified product and in what sequence. If
662 + /// the product is installed, this method accounts for patches that have already been applied to
663 + /// the product and accounts for obsolete and superceded patches.
664 + /// </summary>
665 + /// <param name="product">The product that is the target for the set of patches. This may be
666 + /// either a ProductCode (GUID) of a product that is currently installed, or the path to a an
667 + /// MSI file.</param>
668 + /// <param name="patches">An array of strings specifying the patches to be checked. Each item
669 + /// may be the path to an MSP file, the path an XML file, or just an XML blob.</param>
670 + /// <param name="errorHandler">Callback to be invoked for each inapplicable patch, reporting the
671 + /// reason the patch is not applicable. This value may be left null if that information is not
672 + /// desired.</param>
673 + /// <param name="userSid">Specifies a security identifier (SID) of a user. This parameter restricts
674 + /// the context of enumeration for this user account. This parameter cannot be the special SID
675 + /// strings s-1-1-0 (everyone) or s-1-5-18 (local system). If <paramref name="context"/> is set to
676 + /// <see cref="UserContexts.None"/> or <see cref="UserContexts.Machine"/>, then
677 + /// <paramref name="userSid"/> must be null. For the current user context, <paramref name="userSid"/>
678 + /// can be null and <paramref name="context"/> can be set to <see cref="UserContexts.UserManaged"/>
679 + /// or <see cref="UserContexts.UserUnmanaged"/>.</param>
680 + /// <param name="context">Restricts the enumeration to per-user-unmanaged, per-user-managed,
681 + /// or per-machine context, or (if referring to an MSI) to no system context at all. This
682 + /// parameter can be <see cref="UserContexts.Machine"/>, <see cref="UserContexts.UserManaged"/>,
683 + /// <see cref="UserContexts.UserUnmanaged"/>, or <see cref="UserContexts.None"/>.</param>
684 + /// <returns>An array of selected patch strings from <paramref name="patches"/>, indicating
685 + /// the set of applicable patches. The items are re-ordered to be in the best sequence.</returns>
686 + /// <remarks><p>
687 + /// If an item in <paramref name="patches"/> is a file path but does not end in .MSP or .XML,
688 + /// it is assumed to be an MSP file.
689 + /// </p><p>
690 + /// Passing an InstallContext of None only analyzes the MSI file; it does not consider the
691 + /// current state of the system. You cannot use InstallContext.None with a ProductCode GUID.
692 + /// </p><p>
693 + /// Win32 MSI APIs:
694 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidetermineapplicablepatches.asp">MsiDetermineApplicablePatches</a>
695 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msideterminepatchsequence.asp">MsiDeterminePatchSequence</a>
696 + /// </p></remarks>
697 + public static IList<string> DetermineApplicablePatches(
698 + string product,
699 + string[] patches,
700 + InapplicablePatchHandler errorHandler,
701 + string userSid,
702 + UserContexts context)
703 + {
704 + if (String.IsNullOrEmpty(product))
705 + {
706 + throw new ArgumentNullException("product");
707 + }
708 +
709 + if (patches == null)
710 + {
711 + throw new ArgumentNullException("patches");
712 + }
713 +
714 + NativeMethods.MsiPatchSequenceData[] sequenceData = new NativeMethods.MsiPatchSequenceData[patches.Length];
715 + for (int i = 0; i < patches.Length; i++)
716 + {
717 + if (String.IsNullOrEmpty(patches[i]))
718 + {
719 + throw new ArgumentNullException("patches[" + i + "]");
720 + }
721 +
722 + sequenceData[i].szPatchData = patches[i];
723 + sequenceData[i].ePatchDataType = GetPatchStringDataType(patches[i]);
724 + sequenceData[i].dwOrder = -1;
725 + sequenceData[i].dwStatus = 0;
726 + }
727 +
728 + uint ret;
729 + if (context == UserContexts.None)
730 + {
731 + ret = NativeMethods.MsiDetermineApplicablePatches(product, (uint) sequenceData.Length, sequenceData);
732 + }
733 + else
734 + {
735 + ret = NativeMethods.MsiDeterminePatchSequence(product, userSid, context, (uint) sequenceData.Length, sequenceData);
736 + }
737 +
738 + if (errorHandler != null)
739 + {
740 + for (int i = 0; i < sequenceData.Length; i++)
741 + {
742 + if (sequenceData[i].dwOrder < 0 && sequenceData[i].dwStatus != 0)
743 + {
744 + errorHandler(sequenceData[i].szPatchData, InstallerException.ExceptionFromReturnCode(sequenceData[i].dwStatus));
745 + }
746 + }
747 + }
748 +
749 + if (ret != 0)
750 + {
751 + throw InstallerException.ExceptionFromReturnCode(ret);
752 + }
753 +
754 + IList<string> patchSeq = new List<string>(patches.Length);
755 + for (int i = 0; i < sequenceData.Length; i++)
756 + {
757 + for (int j = 0; j < sequenceData.Length; j++)
758 + {
759 + if (sequenceData[j].dwOrder == i)
760 + {
761 + patchSeq.Add(sequenceData[j].szPatchData);
762 + }
763 + }
764 + }
765 + return patchSeq;
766 + }
767 +
768 + /// <summary>
769 + /// Applies one or more patches to products that are eligible to receive the patch.
770 + /// For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes
771 + /// an installation and sets the PATCH property to the path of the patch package.
772 + /// </summary>
773 + /// <param name="patchPackages">The set of patch packages to be applied.
774 + /// Each item is the full path to an MSP file.</param>
775 + /// <param name="productCode">Provides the ProductCode of the product being patched. If this parameter
776 + /// is null, the patches are applied to all products that are eligible to receive these patches.</param>
777 + /// <param name="commandLine">optional command line property settings</param>
778 + /// <remarks><p>
779 + /// Win32 MSI API:
780 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiapplymultiplepatches.asp">MsiApplyMultiplePatches</a>
781 + /// </p></remarks>
782 + public static void ApplyMultiplePatches(
783 + IList<string> patchPackages, string productCode, string commandLine)
784 + {
785 + if (patchPackages == null || patchPackages.Count == 0)
786 + {
787 + throw new ArgumentNullException("patchPackages");
788 + }
789 +
790 + StringBuilder patchList = new StringBuilder();
791 + foreach (string patch in patchPackages)
792 + {
793 + if (patch != null)
794 + {
795 + if (patchList.Length != 0)
796 + {
797 + patchList.Append(';');
798 + }
799 +
800 + patchList.Append(patch);
801 + }
802 + }
803 +
804 + if (patchList.Length == 0)
805 + {
806 + throw new ArgumentNullException("patchPackages");
807 + }
808 +
809 + uint ret = NativeMethods.MsiApplyMultiplePatches(patchList.ToString(), productCode, commandLine);
810 + Installer.CheckInstallResult(ret);
811 + }
812 +
813 + /// <summary>
814 + /// Extracts information from a patch that can be used to determine whether the patch
815 + /// applies on a target system. The method returns an XML string that can be provided to
816 + /// <see cref="DetermineApplicablePatches(string,string[],InapplicablePatchHandler,string,UserContexts)"/>
817 + /// instead of the full patch file.
818 + /// </summary>
819 + /// <param name="patchPath">Full path to the patch being queried.</param>
820 + /// <returns>XML string containing patch data.</returns>
821 + /// <remarks><p>
822 + /// Win32 MSI API:
823 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiextractpatchxmldata.asp">MsiExtractPatchXMLData</a>
824 + /// </p></remarks>
825 + public static string ExtractPatchXmlData(string patchPath)
826 + {
827 + StringBuilder buf = new StringBuilder("");
828 + uint bufSize = 0;
829 + uint ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize);
830 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
831 + {
832 + buf.Capacity = (int) ++bufSize;
833 + ret = NativeMethods.MsiExtractPatchXMLData(patchPath, 0, buf, ref bufSize);
834 + }
835 +
836 + if (ret != 0)
837 + {
838 + throw InstallerException.ExceptionFromReturnCode(ret);
839 + }
840 + return buf.ToString();
841 + }
842 +
843 + /// <summary>
844 + /// [MSI 3.1] Migrates a user's application configuration data to a new SID.
845 + /// </summary>
846 + /// <param name="oldSid">Previous user SID that data is to be migrated from</param>
847 + /// <param name="newSid">New user SID that data is to be migrated to</param>
848 + /// <remarks><p>
849 + /// Win32 MSI API:
850 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msinotifysidchange.asp">MsiNotifySidChange</a>
851 + /// </p></remarks>
852 + public static void NotifySidChange(string oldSid, string newSid)
853 + {
854 + uint ret = NativeMethods.MsiNotifySidChange(oldSid, newSid);
855 + if (ret != 0)
856 + {
857 + throw InstallerException.ExceptionFromReturnCode(ret);
858 + }
859 + }
860 +
861 + private static void CheckInstallResult(uint ret)
862 + {
863 + switch (ret)
864 + {
865 + case (uint) NativeMethods.Error.SUCCESS: break;
866 + case (uint) NativeMethods.Error.SUCCESS_REBOOT_REQUIRED: Installer.rebootRequired = true; break;
867 + case (uint) NativeMethods.Error.SUCCESS_REBOOT_INITIATED: Installer.rebootInitiated = true; break;
868 + default: throw InstallerException.ExceptionFromReturnCode(ret);
869 + }
870 + }
871 +
872 + private static int GetPatchStringDataType(string patchData)
873 + {
874 + if (patchData.IndexOf("<", StringComparison.Ordinal) >= 0 &&
875 + patchData.IndexOf(">", StringComparison.Ordinal) >= 0)
876 + {
877 + return 2; // XML blob
878 + }
879 + else if (String.Compare(Path.GetExtension(patchData), ".xml",
880 + StringComparison.OrdinalIgnoreCase) == 0)
881 + {
882 + return 1; // XML file path
883 + }
884 + else
885 + {
886 + return 0; // MSP file path
887 + }
888 + }
889 +}
890 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerAdvertise.cs new
+270
@@ -0,0 +1,270 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Reflection;
9 + using System.Globalization;
10 + using System.Collections.Generic;
11 + using System.Diagnostics.CodeAnalysis;
12 +
13 + public static partial class Installer
14 + {
15 + /// <summary>
16 + /// Advertises a product to the local computer.
17 + /// </summary>
18 + /// <param name="packagePath">Path to the package of the product being advertised</param>
19 + /// <param name="perUser">True if the product is user-assigned; false if it is machine-assigned.</param>
20 + /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
21 + /// <param name="locale">The language to use if the source supports multiple languages</param>
22 + /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
23 + /// <seealso cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/>
24 + /// <remarks><p>
25 + /// Win32 MSI APIs:
26 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
27 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
28 + /// </p></remarks>
29 + public static void AdvertiseProduct(string packagePath, bool perUser, string transforms, int locale)
30 + {
31 + if (String.IsNullOrEmpty(packagePath))
32 + {
33 + throw new ArgumentNullException("packagePath");
34 + }
35 +
36 + if (!File.Exists(packagePath))
37 + {
38 + throw new FileNotFoundException(null, packagePath);
39 + }
40 +
41 + uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, new IntPtr(perUser ? 1 : 0), transforms, (ushort) locale);
42 + if (ret != 0)
43 + {
44 + throw InstallerException.ExceptionFromReturnCode(ret);
45 + }
46 + }
47 +
48 + /// <summary>
49 + /// Generates an advertise script. The method enables the installer to write to a
50 + /// script the registry and shortcut information used to assign or publish a product.
51 + /// </summary>
52 + /// <param name="packagePath">Path to the package of the product being advertised</param>
53 + /// <param name="scriptFilePath">path to script file to be created with the advertise information</param>
54 + /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
55 + /// <param name="locale">The language to use if the source supports multiple languages</param>
56 + /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
57 + /// <seealso cref="AdvertiseProduct"/>
58 + /// <remarks><p>
59 + /// Win32 MSI APIs:
60 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
61 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
62 + /// </p></remarks>
63 + public static void GenerateAdvertiseScript(string packagePath, string scriptFilePath, string transforms, int locale)
64 + {
65 + if (String.IsNullOrEmpty(packagePath))
66 + {
67 + throw new ArgumentNullException("packagePath");
68 + }
69 +
70 + if (!File.Exists(packagePath))
71 + {
72 + throw new FileNotFoundException(null, packagePath);
73 + }
74 +
75 + uint ret = NativeMethods.MsiAdvertiseProduct(packagePath, scriptFilePath, transforms, (ushort) locale);
76 + if (ret != 0)
77 + {
78 + throw InstallerException.ExceptionFromReturnCode(ret);
79 + }
80 + }
81 +
82 + /// <summary>
83 + /// Generates an advertise script. The method enables the installer to write to a
84 + /// script the registry and shortcut information used to assign or publish a product.
85 + /// </summary>
86 + /// <param name="packagePath">Path to the package of the product being advertised</param>
87 + /// <param name="scriptFilePath">path to script file to be created with the advertise information</param>
88 + /// <param name="transforms">Semi-colon delimited list of transforms to be applied. This parameter may be null.</param>
89 + /// <param name="locale">The language to use if the source supports multiple languages</param>
90 + /// <param name="processor">Targeted processor architecture.</param>
91 + /// <param name="instance">True to install multiple instances through product code changing transform.
92 + /// Advertises a new instance of the product. Requires that the <paramref name="transforms"/> parameter
93 + /// includes the instance transform that changes the product code.</param>
94 + /// <seealso cref="AdvertiseProduct"/>
95 + /// <remarks><p>
96 + /// Win32 MSI APIs:
97 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproduct.asp">MsiAdvertiseProduct</a>,
98 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiadvertiseproductex.asp">MsiAdvertiseProductEx</a>
99 + /// </p></remarks>
100 + public static void GenerateAdvertiseScript(
101 + string packagePath,
102 + string scriptFilePath,
103 + string transforms,
104 + int locale,
105 + ProcessorArchitecture processor,
106 + bool instance)
107 + {
108 + if (String.IsNullOrEmpty(packagePath))
109 + {
110 + throw new ArgumentNullException("packagePath");
111 + }
112 +
113 + if (String.IsNullOrEmpty(scriptFilePath))
114 + {
115 + throw new ArgumentNullException("scriptFilePath");
116 + }
117 +
118 + if (!File.Exists(packagePath))
119 + {
120 + throw new FileNotFoundException(null, packagePath);
121 + }
122 +
123 + uint platform = 0;
124 + switch (processor)
125 + {
126 + case ProcessorArchitecture.X86: platform = (uint) 1; break;
127 + case ProcessorArchitecture.IA64: platform = (uint) 2; break;
128 + case ProcessorArchitecture.Amd64: platform = (uint) 4; break;
129 + }
130 +
131 + uint ret = NativeMethods.MsiAdvertiseProductEx(
132 + packagePath,
133 + scriptFilePath,
134 + transforms,
135 + (ushort) locale,
136 + platform,
137 + instance ? (uint) 1 : 0);
138 + if (ret != 0)
139 + {
140 + throw InstallerException.ExceptionFromReturnCode(ret);
141 + }
142 + }
143 +
144 + /// <summary>
145 + /// Copies an advertise script file to the local computer.
146 + /// </summary>
147 + /// <param name="scriptFile">Path to a script file generated by
148 + /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
149 + /// <param name="flags">Flags controlling advertisement</param>
150 + /// <param name="removeItems">True if specified items are to be removed instead of being created</param>
151 + /// <remarks><p>
152 + /// The process calling this function must be running under the LocalSystem account. To advertise an
153 + /// application for per-user installation to a targeted user, the thread that calls this function must
154 + /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted
155 + /// user, the application is advertised to all users for installation with elevated privileges.
156 + /// </p></remarks>
157 + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "flags")]
158 + public static void AdvertiseScript(string scriptFile, int flags, bool removeItems)
159 + {
160 + uint ret = NativeMethods.MsiAdvertiseScript(scriptFile, (uint) flags, IntPtr.Zero, removeItems);
161 + if (ret != 0)
162 + {
163 + throw InstallerException.ExceptionFromReturnCode(ret);
164 + }
165 + }
166 +
167 + /// <summary>
168 + /// Processes an advertise script file into the specified locations.
169 + /// </summary>
170 + /// <param name="scriptFile">Path to a script file generated by
171 + /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
172 + /// <param name="iconFolder">An optional path to a folder in which advertised icon files and transform
173 + /// files are located. If this parameter is null, no icon or transform files are written.</param>
174 + /// <param name="shortcuts">True if shortcuts should be created</param>
175 + /// <param name="removeItems">True if specified items are to be removed instead of created</param>
176 + /// <remarks><p>
177 + /// The process calling this function must be running under the LocalSystem account. To advertise an
178 + /// application for per-user installation to a targeted user, the thread that calls this function must
179 + /// impersonate the targeted user. If the thread calling this function is not impersonating a targeted
180 + /// user, the application is advertised to all users for installation with elevated privileges.
181 + /// </p><p>
182 + /// Win32 MSI API:
183 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessadvertisescript.asp">MsiProcessAdvertiseScript</a>
184 + /// </p></remarks>
185 + public static void ProcessAdvertiseScript(string scriptFile, string iconFolder, bool shortcuts, bool removeItems)
186 + {
187 + uint ret = NativeMethods.MsiProcessAdvertiseScript(scriptFile, iconFolder, IntPtr.Zero, shortcuts, removeItems);
188 + if (ret != 0)
189 + {
190 + throw InstallerException.ExceptionFromReturnCode(ret);
191 + }
192 + }
193 +
194 + /// <summary>
195 + /// Gets product information for an installer script file.
196 + /// </summary>
197 + /// <param name="scriptFile">Path to a script file generated by
198 + /// <see cref="GenerateAdvertiseScript(string,string,string,int,ProcessorArchitecture,bool)"/></param>
199 + /// <returns>ProductInstallation stub with advertise-related properties filled in.</returns>
200 + /// <exception cref="ArgumentOutOfRangeException">An invalid product property was requested</exception>
201 + /// <remarks><p>
202 + /// Only the following properties will be filled in in the returned object:<ul>
203 + /// <li><see cref="ProductInstallation.ProductCode"/></li>
204 + /// <li><see cref="ProductInstallation.AdvertisedLanguage"/></li>
205 + /// <li><see cref="ProductInstallation.AdvertisedVersion"/></li>
206 + /// <li><see cref="ProductInstallation.AdvertisedProductName"/></li>
207 + /// <li><see cref="ProductInstallation.AdvertisedPackageName"/></li>
208 + /// </ul>Other properties will be null.
209 + /// </p><p>
210 + /// Win32 MSI API:
211 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfofromscript.asp">MsiGetProductInfoFromScript</a>
212 + /// </p></remarks>
213 + public static ProductInstallation GetProductInfoFromScript(string scriptFile)
214 + {
215 + if (String.IsNullOrEmpty(scriptFile))
216 + {
217 + throw new ArgumentNullException("scriptFile");
218 + }
219 + StringBuilder productCodeBuf = new StringBuilder(40);
220 + ushort lang;
221 + uint ver;
222 + StringBuilder productNameBuf = new StringBuilder(100);
223 + StringBuilder packageNameBuf = new StringBuilder(40);
224 + uint productCodeBufSize = (uint) productCodeBuf.Capacity;
225 + uint productNameBufSize = (uint) productNameBuf.Capacity;
226 + uint packageNameBufSize = (uint) packageNameBuf.Capacity;
227 + uint ret = NativeMethods.MsiGetProductInfoFromScript(
228 + scriptFile,
229 + productCodeBuf,
230 + out lang,
231 + out ver,
232 + productNameBuf,
233 + ref productNameBufSize,
234 + packageNameBuf,
235 + ref packageNameBufSize);
236 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
237 + {
238 + productCodeBuf.Capacity = (int) ++productCodeBufSize;
239 + productNameBuf.Capacity = (int) ++productNameBufSize;
240 + packageNameBuf.Capacity = (int) ++packageNameBufSize;
241 + ret = NativeMethods.MsiGetProductInfoFromScript(
242 + scriptFile,
243 + productCodeBuf,
244 + out lang,
245 + out ver,
246 + productNameBuf,
247 + ref productNameBufSize,
248 + packageNameBuf,
249 + ref packageNameBufSize);
250 + }
251 +
252 + if (ret != 0)
253 + {
254 + throw InstallerException.ExceptionFromReturnCode(ret);
255 + }
256 + uint verPart1 = ver >> 24;
257 + uint verPart2 = (ver & 0x00FFFFFF) >> 16;
258 + uint verPart3 = ver & 0x0000FFFF;
259 + Version version = new Version((int) verPart1, (int) verPart2, (int) verPart3);
260 +
261 + IDictionary<string, string> props = new Dictionary<string, string>();
262 + props["ProductCode"] = productCodeBuf.ToString();
263 + props["Language"] = lang.ToString(CultureInfo.InvariantCulture);
264 + props["Version"] = version.ToString();
265 + props["ProductName"] = productNameBuf.ToString();
266 + props["PackageName"] = packageNameBuf.ToString();
267 + return new ProductInstallation(props);
268 + }
269 + }
270 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/InstallerUtils.cs new
+472
@@ -0,0 +1,472 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Resources;
9 + using System.Reflection;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Runtime.InteropServices;
13 + using System.Diagnostics.CodeAnalysis;
14 +
15 + public static partial class Installer
16 + {
17 + /// <summary>
18 + /// Gets the current version of the installer.
19 + /// </summary>
20 + public static Version Version
21 + {
22 + get
23 + {
24 + // TODO: Use the extended form of version info to get the 4th component of the verison.
25 + uint[] dllVersionInfo = new uint[5];
26 + dllVersionInfo[0] = 20;
27 + int hr = NativeMethods.DllGetVersion(dllVersionInfo);
28 + if (hr != 0)
29 + {
30 + Marshal.ThrowExceptionForHR(hr);
31 + }
32 +
33 + return new Version((int) dllVersionInfo[1], (int) dllVersionInfo[2], (int) dllVersionInfo[3]);
34 + }
35 + }
36 +
37 + internal static ResourceManager ErrorResources
38 + {
39 + get
40 + {
41 + if (errorResources == null)
42 + {
43 + errorResources = new ResourceManager(typeof(Installer).Namespace + ".Errors", typeof(Installer).Assembly);
44 + }
45 + return errorResources;
46 + }
47 + }
48 +
49 + /// <summary>
50 + /// Gets a Windows Installer error message in the system default language.
51 + /// </summary>
52 + /// <param name="errorNumber">The error number.</param>
53 + /// <returns>The message string, or null if the error message is not found.</returns>
54 + /// <remarks><p>
55 + /// The returned string may have tokens such as [2] and [3] that are meant to be substituted
56 + /// with context-specific values.
57 + /// </p><p>
58 + /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
59 + /// returned in English.
60 + /// </p></remarks>
61 + public static string GetErrorMessage(int errorNumber)
62 + {
63 + return Installer.GetErrorMessage(errorNumber, null);
64 + }
65 +
66 + /// <summary>
67 + /// Gets a Windows Installer error message in a specified language.
68 + /// </summary>
69 + /// <param name="errorNumber">The error number.</param>
70 + /// <param name="culture">The locale for the message.</param>
71 + /// <returns>The message string, or null if the error message or locale is not found.</returns>
72 + /// <remarks><p>
73 + /// The returned string may have tokens such as [2] and [3] that are meant to be substituted
74 + /// with context-specific values.
75 + /// </p><p>
76 + /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
77 + /// returned in English.
78 + /// </p></remarks>
79 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
80 + public static string GetErrorMessage(int errorNumber, CultureInfo culture)
81 + {
82 + if (culture == null)
83 + {
84 + culture = CultureInfo.CurrentCulture;
85 + }
86 +
87 + string msg = Installer.ErrorResources.GetString(
88 + errorNumber.ToString(CultureInfo.InvariantCulture.NumberFormat),
89 + culture);
90 + if (msg == null)
91 + {
92 + string msiMsgModule = Path.Combine(
93 + Environment.SystemDirectory, "msimsg.dll");
94 + msg = Installer.GetMessageFromModule(
95 + msiMsgModule, errorNumber, culture);
96 + }
97 + return msg;
98 + }
99 +
100 + private static string GetMessageFromModule(
101 + string modulePath, int errorNumber, CultureInfo culture)
102 + {
103 + const uint LOAD_LIBRARY_AS_DATAFILE = 2;
104 + const int RT_RCDATA = 10;
105 +
106 + IntPtr msgModule = NativeMethods.LoadLibraryEx(
107 + modulePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
108 + if (msgModule == IntPtr.Zero)
109 + {
110 + return null;
111 + }
112 +
113 + try
114 + {
115 + // On pre-Vista systems, the messages are stored as RCDATA resources.
116 +
117 + int lcid = (culture == CultureInfo.InvariantCulture) ?
118 + 0 : culture.LCID;
119 + IntPtr resourceInfo = NativeMethods.FindResourceEx(
120 + msgModule,
121 + new IntPtr(RT_RCDATA),
122 + new IntPtr(errorNumber),
123 + (ushort) lcid);
124 + if (resourceInfo != IntPtr.Zero)
125 + {
126 + IntPtr resourceData = NativeMethods.LoadResource(
127 + msgModule, resourceInfo);
128 + IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
129 +
130 + if (lcid == 0)
131 + {
132 + string msg = Marshal.PtrToStringAnsi(resourcePtr);
133 + return msg;
134 + }
135 + else
136 + {
137 + int len = 0;
138 + while (Marshal.ReadByte(resourcePtr, len) != 0)
139 + {
140 + len++;
141 + }
142 + byte[] msgBytes = new byte[len + 1];
143 + Marshal.Copy(resourcePtr, msgBytes, 0, msgBytes.Length);
144 + Encoding encoding = Encoding.GetEncoding(
145 + culture.TextInfo.ANSICodePage);
146 + string msg = encoding.GetString(msgBytes);
147 + return msg;
148 + }
149 + }
150 + else
151 + {
152 + // On Vista (and above?), the messages are stored in the module message table.
153 + // They're actually in MUI files, and the redirection happens automatically here.
154 +
155 + const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
156 + const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
157 + const uint MESSAGE_OFFSET = 20000; // Not documented, but observed on Vista
158 +
159 + StringBuilder buf = new StringBuilder(1024);
160 + uint formatCount = NativeMethods.FormatMessage(
161 + FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
162 + msgModule,
163 + (uint) errorNumber + MESSAGE_OFFSET,
164 + (ushort) lcid,
165 + buf,
166 + (uint) buf.Capacity,
167 + IntPtr.Zero);
168 +
169 + return formatCount != 0 ? buf.ToString().Trim() : null;
170 + }
171 + }
172 + finally
173 + {
174 + NativeMethods.FreeLibrary(msgModule);
175 + }
176 + }
177 +
178 + /// <summary>
179 + /// Gets a formatted Windows Installer error message in the system default language.
180 + /// </summary>
181 + /// <param name="errorRecord">Error record containing the error number in the first field, and
182 + /// error-specific parameters in the other fields.</param>
183 + /// <returns>The message string, or null if the error message is not found.</returns>
184 + /// <remarks><p>
185 + /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
186 + /// returned in English.
187 + /// </p></remarks>
188 + public static string GetErrorMessage(Record errorRecord) { return Installer.GetErrorMessage(errorRecord, null); }
189 +
190 + /// <summary>
191 + /// Gets a formatted Windows Installer error message in a specified language.
192 + /// </summary>
193 + /// <param name="errorRecord">Error record containing the error number in the first field, and
194 + /// error-specific parameters in the other fields.</param>
195 + /// <param name="culture">The locale for the message.</param>
196 + /// <returns>The message string, or null if the error message or locale is not found.</returns>
197 + /// <remarks><p>
198 + /// Error numbers greater than 2000 refer to MSI "internal" errors, and are always
199 + /// returned in English.
200 + /// </p></remarks>
201 + public static string GetErrorMessage(Record errorRecord, CultureInfo culture)
202 + {
203 + if (errorRecord == null)
204 + {
205 + throw new ArgumentNullException("errorRecord");
206 + }
207 + int errorNumber;
208 + if (errorRecord.FieldCount < 1 || (errorNumber = (int) errorRecord.GetInteger(1)) == 0)
209 + {
210 + throw new ArgumentOutOfRangeException("errorRecord");
211 + }
212 +
213 + string msg = Installer.GetErrorMessage(errorNumber, culture);
214 + if (msg != null)
215 + {
216 + errorRecord.FormatString = msg;
217 + msg = errorRecord.ToString((IFormatProvider)null);
218 + }
219 + return msg;
220 + }
221 +
222 + /// <summary>
223 + /// Gets the version string of the path specified using the format that the installer
224 + /// expects to find it in in the database.
225 + /// </summary>
226 + /// <param name="path">Path to the file</param>
227 + /// <returns>Version string in the "#.#.#.#" format, or an empty string if the file
228 + /// does not contain version information</returns>
229 + /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception>
230 + /// <remarks><p>
231 + /// Win32 MSI API:
232 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a>
233 + /// </p></remarks>
234 + public static string GetFileVersion(string path)
235 + {
236 + StringBuilder version = new StringBuilder(20);
237 + uint verBufSize = 0, langBufSize = 0;
238 + uint ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize);
239 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
240 + {
241 + version.Capacity = (int) ++verBufSize;
242 + ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize);
243 + }
244 +
245 + if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
246 + {
247 + if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
248 + ret == (uint) NativeMethods.Error.ACCESS_DENIED)
249 + {
250 + throw new FileNotFoundException(null, path);
251 + }
252 + else
253 + {
254 + throw InstallerException.ExceptionFromReturnCode(ret);
255 + }
256 + }
257 + return version.ToString();
258 + }
259 +
260 + /// <summary>
261 + /// Gets the language string of the path specified using the format that the installer
262 + /// expects to find them in in the database.
263 + /// </summary>
264 + /// <param name="path">Path to the file</param>
265 + /// <returns>Language string in the form of a decimal language ID, or an empty string if the file
266 + /// does not contain a language ID</returns>
267 + /// <exception cref="FileNotFoundException">the file does not exist or could not be read</exception>
268 + /// <remarks><p>
269 + /// Win32 MSI API:
270 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfileversion.asp">MsiGetFileVersion</a>
271 + /// </p></remarks>
272 + public static string GetFileLanguage(string path)
273 + {
274 + StringBuilder language = new StringBuilder("", 10);
275 + uint verBufSize = 0, langBufSize = 0;
276 + uint ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize);
277 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
278 + {
279 + language.Capacity = (int) ++langBufSize;
280 + ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize);
281 + }
282 +
283 + if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID)
284 + {
285 + if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
286 + ret == (uint) NativeMethods.Error.ACCESS_DENIED)
287 + {
288 + throw new FileNotFoundException(null, path);
289 + }
290 + else
291 + {
292 + throw InstallerException.ExceptionFromReturnCode(ret);
293 + }
294 + }
295 + return language.ToString();
296 + }
297 +
298 + /// <summary>
299 + /// Gets a 128-bit hash of the specified file.
300 + /// </summary>
301 + /// <param name="path">Path to the file</param>
302 + /// <param name="hash">Integer array of length 4 which receives the
303 + /// four 32-bit parts of the hash value.</param>
304 + /// <exception cref="FileNotFoundException">the file does not exist or
305 + /// could not be read</exception>
306 + /// <remarks><p>
307 + /// Win32 MSI API:
308 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetfilehash.asp">MsiGetFileHash</a>
309 + /// </p></remarks>
310 + public static void GetFileHash(string path, int[] hash)
311 + {
312 + if (hash == null)
313 + {
314 + throw new ArgumentNullException("hash");
315 + }
316 +
317 + uint[] tempHash = new uint[5];
318 + tempHash[0] = 20;
319 + uint ret = NativeMethods.MsiGetFileHash(path, 0, tempHash);
320 + if (ret != 0)
321 + {
322 + if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
323 + ret == (uint) NativeMethods.Error.ACCESS_DENIED)
324 + {
325 + throw new FileNotFoundException(null, path);
326 + }
327 + else
328 + {
329 + throw InstallerException.ExceptionFromReturnCode(ret);
330 + }
331 + }
332 +
333 + for (int i = 0; i < 4; i++)
334 + {
335 + hash[i] = unchecked ((int) tempHash[i + 1]);
336 + }
337 + }
338 +
339 + /// <summary>
340 + /// Examines a shortcut and returns its product, feature name, and component if available.
341 + /// </summary>
342 + /// <param name="shortcut">Full path to a shortcut</param>
343 + /// <returns>ShortcutTarget structure containing target product code, feature, and component code</returns>
344 + /// <remarks><p>
345 + /// Win32 MSI API:
346 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetshortcuttarget.asp">MsiGetShortcutTarget</a>
347 + /// </p></remarks>
348 + public static ShortcutTarget GetShortcutTarget(string shortcut)
349 + {
350 + StringBuilder productBuf = new StringBuilder(40);
351 + StringBuilder featureBuf = new StringBuilder(40);
352 + StringBuilder componentBuf = new StringBuilder(40);
353 +
354 + uint ret = NativeMethods.MsiGetShortcutTarget(shortcut, productBuf, featureBuf, componentBuf);
355 + if (ret != 0)
356 + {
357 + throw InstallerException.ExceptionFromReturnCode(ret);
358 + }
359 + return new ShortcutTarget(
360 + productBuf.Length > 0 ? productBuf.ToString() : null,
361 + featureBuf.Length > 0 ? featureBuf.ToString() : null,
362 + componentBuf.Length > 0 ? componentBuf.ToString() : null);
363 + }
364 +
365 + /// <summary>
366 + /// Verifies that the given file is an installation package.
367 + /// </summary>
368 + /// <param name="packagePath">Path to the package</param>
369 + /// <returns>True if the file is an installation package; false otherwise.</returns>
370 + /// <exception cref="FileNotFoundException">the specified package file does not exist</exception>
371 + /// <exception cref="InstallerException">the package file could not be opened</exception>
372 + /// <remarks><p>
373 + /// Win32 MSI API:
374 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifypackage.asp">MsiVerifyPackage</a>
375 + /// </p></remarks>
376 + public static bool VerifyPackage(string packagePath)
377 + {
378 + if (String.IsNullOrEmpty(packagePath))
379 + {
380 + throw new ArgumentNullException("packagePath");
381 + }
382 +
383 + if (!File.Exists(packagePath))
384 + {
385 + throw new FileNotFoundException(null, packagePath);
386 + }
387 +
388 + uint ret = NativeMethods.MsiVerifyPackage(packagePath);
389 + if (ret == (uint) NativeMethods.Error.INSTALL_PACKAGE_INVALID)
390 + {
391 + return false;
392 + }
393 + else if (ret != 0)
394 + {
395 + throw InstallerException.ExceptionFromReturnCode(ret);
396 + }
397 + return true;
398 + }
399 +
400 + /// <summary>
401 + /// [MSI 4.0] Gets the list of files that can be updated by one or more patches.
402 + /// </summary>
403 + /// <param name="productCode">ProductCode (GUID) of the product which is
404 + /// the target of the patches</param>
405 + /// <param name="patches">list of file paths of one or more patches to be
406 + /// analyzed</param>
407 + /// <returns>List of absolute paths of files that can be updated when the
408 + /// patches are applied on this system.</returns>
409 + /// <remarks><p>
410 + /// Win32 MSI API:
411 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchfilelist.asp">MsiGetPatchFileList</a>
412 + /// </p></remarks>
413 + public static IList<string> GetPatchFileList(string productCode, IList<string> patches)
414 + {
415 + if (String.IsNullOrEmpty(productCode))
416 + {
417 + throw new ArgumentNullException("productCode");
418 + }
419 +
420 + if (patches == null || patches.Count == 0)
421 + {
422 + throw new ArgumentNullException("patches");
423 + }
424 +
425 + StringBuilder patchList = new StringBuilder();
426 + foreach (string patch in patches)
427 + {
428 + if (patch != null)
429 + {
430 + if (patchList.Length != 0)
431 + {
432 + patchList.Append(';');
433 + }
434 +
435 + patchList.Append(patch);
436 + }
437 + }
438 +
439 + if (patchList.Length == 0)
440 + {
441 + throw new ArgumentNullException("patches");
442 + }
443 +
444 + IntPtr phFileRecords;
445 + uint cFiles;
446 +
447 + uint ret = NativeMethods.MsiGetPatchFileList(
448 + productCode,
449 + patchList.ToString(),
450 + out cFiles,
451 + out phFileRecords);
452 + if (ret != 0)
453 + {
454 + throw InstallerException.ExceptionFromReturnCode(ret);
455 + }
456 +
457 + List<string> files = new List<string>();
458 +
459 + for (uint i = 0; i < cFiles; i++)
460 + {
461 + int hFileRec = Marshal.ReadInt32(phFileRecords, (int) i);
462 +
463 + using (Record fileRec = new Record(hFileRec, true, null))
464 + {
465 + files.Add(fileRec.GetString(1));
466 + }
467 + }
468 +
469 + return files;
470 + }
471 + }
472 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/MediaDisk.cs new
+58
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Diagnostics.CodeAnalysis;
7 +
8 + /// <summary>
9 + /// Represents a media disk source of a product or a patch.
10 + /// </summary>
11 + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
12 + public struct MediaDisk
13 + {
14 + private int diskId;
15 + private string volumeLabel;
16 + private string diskPrompt;
17 +
18 + /// <summary>
19 + /// Creates a new media disk.
20 + /// </summary>
21 + /// <param name="diskId"></param>
22 + /// <param name="volumeLabel"></param>
23 + /// <param name="diskPrompt"></param>
24 + public MediaDisk(int diskId, string volumeLabel, string diskPrompt)
25 + {
26 + this.diskId = diskId;
27 + this.volumeLabel = volumeLabel;
28 + this.diskPrompt = diskPrompt;
29 + }
30 +
31 + /// <summary>
32 + /// Gets or sets the disk id of the media disk.
33 + /// </summary>
34 + public int DiskId
35 + {
36 + get { return this.diskId; }
37 + set { this.diskId = value; }
38 + }
39 +
40 + /// <summary>
41 + /// Gets or sets the volume label of the media disk.
42 + /// </summary>
43 + public string VolumeLabel
44 + {
45 + get { return this.volumeLabel; }
46 + set { this.volumeLabel = value; }
47 + }
48 +
49 + /// <summary>
50 + /// Gets or sets the disk prompt of the media disk.
51 + /// </summary>
52 + public string DiskPrompt
53 + {
54 + get { return this.diskPrompt; }
55 + set { this.diskPrompt = value; }
56 + }
57 + }
58 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/NativeMethods.cs new
+309
@@ -0,0 +1,309 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 +using System;
6 +using System.Text;
7 +using System.Runtime.InteropServices;
8 +using System.Diagnostics.CodeAnalysis;
9 +
10 +using IStream = System.Runtime.InteropServices.ComTypes.IStream;
11 +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
12 +using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
13 +
14 +[Guid("0000000b-0000-0000-C000-000000000046")]
15 +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
16 +internal interface IStorage
17 +{
18 + [return: MarshalAs(UnmanagedType.Interface)]
19 + IStream CreateStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2);
20 + [return: MarshalAs(UnmanagedType.Interface)]
21 + IStream OpenStream([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr reserved1, uint grfMode, uint reserved2);
22 + [return: MarshalAs(UnmanagedType.Interface)]
23 + IStorage CreateStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved1, uint reserved2);
24 + [return: MarshalAs(UnmanagedType.Interface)]
25 + IStorage OpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved);
26 + void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest);
27 + void MoveElementTo([MarshalAs(UnmanagedType.LPWStr)] string wcsName, [MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName, uint grfFlags);
28 + void Commit(uint grfCommitFlags);
29 + void Revert();
30 + IntPtr EnumElements(uint reserved1, IntPtr reserved2, uint reserved3);
31 + void DestroyElement([MarshalAs(UnmanagedType.LPWStr)] string wcsName);
32 + void RenameElement([MarshalAs(UnmanagedType.LPWStr)] string wcsOldName, [MarshalAs(UnmanagedType.LPWStr)] string wcsNewName);
33 + void SetElementTimes([MarshalAs(UnmanagedType.LPWStr)] string wcsName, ref FILETIME ctime, ref FILETIME atime, ref FILETIME mtime);
34 + void SetClass(ref Guid clsid);
35 + void SetStateBits(uint grfStateBits, uint grfMask);
36 + void Stat(ref STATSTG statstg, uint grfStatFlag);
37 +}
38 +
39 +internal static class NativeMethods
40 +{
41 + internal enum Error : uint
42 + {
43 + SUCCESS = 0,
44 + FILE_NOT_FOUND = 2,
45 + PATH_NOT_FOUND = 3,
46 + ACCESS_DENIED = 5,
47 + INVALID_HANDLE = 6,
48 + INVALID_DATA = 13,
49 + INVALID_PARAMETER = 87,
50 + OPEN_FAILED = 110,
51 + DISK_FULL = 112,
52 + CALL_NOT_IMPLEMENTED = 120,
53 + BAD_PATHNAME = 161,
54 + NO_DATA = 232,
55 + MORE_DATA = 234,
56 + NO_MORE_ITEMS = 259,
57 + DIRECTORY = 267,
58 + INSTALL_USEREXIT = 1602,
59 + INSTALL_FAILURE = 1603,
60 + FILE_INVALID = 1006,
61 + UNKNOWN_PRODUCT = 1605,
62 + UNKNOWN_FEATURE = 1606,
63 + UNKNOWN_COMPONENT = 1607,
64 + UNKNOWN_PROPERTY = 1608,
65 + INVALID_HANDLE_STATE = 1609,
66 + INSTALL_SOURCE_ABSENT = 1612,
67 + BAD_QUERY_SYNTAX = 1615,
68 + INSTALL_PACKAGE_INVALID = 1620,
69 + FUNCTION_FAILED = 1627,
70 + INVALID_TABLE = 1628,
71 + DATATYPE_MISMATCH = 1629,
72 + CREATE_FAILED = 1631,
73 + SUCCESS_REBOOT_INITIATED = 1641,
74 + SUCCESS_REBOOT_REQUIRED = 3010,
75 + }
76 +
77 + internal enum SourceType : int
78 + {
79 + Unknown = 0,
80 + Network = 1,
81 + Url = 2,
82 + Media = 3,
83 + }
84 +
85 + [Flags]
86 + internal enum STGM : uint
87 + {
88 + DIRECT = 0x00000000,
89 + TRANSACTED = 0x00010000,
90 + SIMPLE = 0x08000000,
91 +
92 + READ = 0x00000000,
93 + WRITE = 0x00000001,
94 + READWRITE = 0x00000002,
95 +
96 + SHARE_DENY_NONE = 0x00000040,
97 + SHARE_DENY_READ = 0x00000030,
98 + SHARE_DENY_WRITE = 0x00000020,
99 + SHARE_EXCLUSIVE = 0x00000010,
100 +
101 + PRIORITY = 0x00040000,
102 + DELETEONRELEASE = 0x04000000,
103 + NOSCRATCH = 0x00100000,
104 +
105 + CREATE = 0x00001000,
106 + CONVERT = 0x00020000,
107 + FAILIFTHERE = 0x00000000,
108 +
109 + NOSNAPSHOT = 0x00200000,
110 + DIRECT_SWMR = 0x00400000,
111 + }
112 +
113 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int DllGetVersion(uint[] dvi);
114 +
115 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, ref IntPtr phWnd);
116 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInternalUI(uint dwUILevel, IntPtr phWnd);
117 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern NativeExternalUIHandler MsiSetExternalUI([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIHandler puiHandler, uint dwMessageFilter, IntPtr pvContext);
118 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnableLog(uint dwLogMode, string szLogFile, uint dwLogAttributes);
119 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProducts(uint iProductIndex, StringBuilder lpProductBuf);
120 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfo(string szProduct, string szProperty, StringBuilder lpValueBuf, ref uint pcchValueBuf);
121 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatches(string szProduct, uint iPatchIndex, StringBuilder lpPatchBuf, StringBuilder lpTransformsBuf, ref uint pcchTransformsBuf);
122 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfo(string szPatch, string szAttribute, StringBuilder lpValueBuf, ref uint pcchValueBuf);
123 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumFeatures(string szProduct, uint iFeatureIndex, StringBuilder lpFeatureBuf, StringBuilder lpParentBuf);
124 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryFeatureState(string szProduct, string szFeature);
125 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiUseFeatureEx(string szProduct, string szFeature, uint dwInstallMode, uint dwReserved);
126 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiQueryProductState(string szProduct);
127 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetShortcutTarget(string szShortcut, StringBuilder szProductCode, StringBuilder szFeatureId, StringBuilder szComponentCode);
128 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideComponent(string szProduct, string szFeature, string szComponent, uint dwInstallMode, StringBuilder lpPathBuf, ref uint cchPathBuf);
129 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideQualifiedComponentEx(string szComponent, string szQualifier, uint dwInstallMode, string szProduct, uint dwUnused1, uint dwUnused2, StringBuilder lpPathBuf, ref uint cchPathBuf);
130 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallFeature(string szFeature, string szProduct, uint dwReinstallMode);
131 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiReinstallProduct(string szProduct, uint dwReinstallMode);
132 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSource(string szProduct, string szUserName, uint dwReserved, string szSource);
133 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAll(string szProduct, string szUserName, uint dwReserved);
134 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolution(string szProduct, string szUserName, uint dwReserved);
135 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCollectUserInfo(string szProduct);
136 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetUserInfo(string szProduct, StringBuilder lpUserNameBuf, ref uint cchUserNameBuf, StringBuilder lpOrgNameBuf, ref uint cchOrgNameBuf, StringBuilder lpSerialBuf, ref uint cchSerialBuf);
137 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenPackageEx(string szPackagePath, uint dwOptions, out int hProduct);
138 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenProduct(string szProduct, out int hProduct);
139 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallProduct(string szPackagePath, string szCommandLine);
140 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureProductEx(string szProduct, int iInstallLevel, int eInstallState, string szCommandLine);
141 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiConfigureFeature(string szProduct, string szFeature, int eInstallState);
142 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine);
143 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, IntPtr uiOpenMode, out int hDatabase);
144 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiOpenDatabase(string szDatabasePath, string szPersist, out int hDatabase);
145 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetDatabaseState(int hDatabase);
146 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView);
147 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseMerge(int hDatabase, int hDatabaseMerge, string szTableName);
148 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseCommit(int hDatabase);
149 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord);
150 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName);
151 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseExport(int hDatabase, string szTableName, string szFolderPath, string szFileName);
152 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseImport(int hDatabase, string szFolderPath, string szFileName);
153 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseGenerateTransform(int hDatabase, int hDatabaseReference, string szTransformFile, int iReserved1, int iReserved2);
154 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCreateTransformSummaryInfo(int hDatabase, int hDatabaseReference, string szTransformFile, int iErrorConditions, int iValidation);
155 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDatabaseApplyTransform(int hDatabase, string szTransformFile, int iErrorConditions);
156 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewExecute(int hView, int hRecord);
157 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewFetch(int hView, out int hRecord);
158 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewModify(int hView, int iModifyMode, int hRecord);
159 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf);
160 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord);
161 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiCreateRecord(uint cParams);
162 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf);
163 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordClearData(int hRecord);
164 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetFieldCount(int hRecord);
165 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiRecordIsNull(int hRecord, uint iField);
166 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiRecordGetInteger(int hRecord, uint iField);
167 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf);
168 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetInteger(int hRecord, uint iField, int iValue);
169 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetString(int hRecord, uint iField, string szValue);
170 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordDataSize(int hRecord, uint iField);
171 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf);
172 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath);
173 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo);
174 +//[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetPropertyCount(int hSummaryInfo, out uint uiPropertyCount);
175 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf);
176 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoSetProperty(int hSummaryInfo, uint uiProperty, uint uiDataType, int iValue, ref long ftValue, string szValue);
177 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSummaryInfoPersist(int hSummaryInfo);
178 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiCloseHandle(int hAny);
179 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileVersion(string szFilePath, StringBuilder szVersionBuf, ref uint cchVersionBuf, StringBuilder szLangBuf, ref uint cchLangBuf);
180 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFileHash(string szFilePath, uint dwOptions, uint[] hash);
181 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetActiveDatabase(int hInstall);
182 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf);
183 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetProperty(int hInstall, string szName, string szValue);
184 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord);
185 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEvaluateCondition(int hInstall, string szCondition);
186 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool MsiGetMode(int hInstall, uint iRunMode);
187 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetMode(int hInstall, uint iRunMode, [MarshalAs(UnmanagedType.Bool)] bool fState);
188 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDoAction(int hInstall, string szAction);
189 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSequence(int hInstall, string szTable, int iSequenceMode);
190 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf);
191 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf);
192 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath);
193 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction);
194 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetComponentState(int hInstall, string szComponent, int iState);
195 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction);
196 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureState(int hInstall, string szFeature, int iState);
197 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstallState);
198 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetInstallLevel(int hInstall, int iInstallLevel);
199 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern ushort MsiGetLanguage(int hInstall);
200 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponents(uint iComponentIndex, StringBuilder lpComponentBuf);
201 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentsEx(string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
202 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClients(string szComponent, uint iProductIndex, StringBuilder lpProductBuf);
203 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumClientsEx(string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint iProductIndex, StringBuilder lpProductBuf, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
204 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPath(string szProduct, string szComponent, StringBuilder lpPathBuf, ref uint pcchBuf);
205 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetComponentPathEx(string szProduct, string szComponent, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, StringBuilder lpPathBuf, ref uint pcchBuf);
206 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentQualifiers(string szComponent, uint iIndex, StringBuilder lpQualifierBuf, ref uint pcchQualifierBuf, StringBuilder lpApplicationDataBuf, ref uint pcchApplicationDataBuf);
207 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiGetLastErrorRecord();
208 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumRelatedProducts(string upgradeCode, uint dwReserved, uint iProductIndex, StringBuilder lpProductBuf);
209 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductCode(string szComponent, StringBuilder lpProductBuf);
210 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureUsage(string szProduct, string szFeature, out uint dwUseCount, out ushort dwDateUsed);
211 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost);
212 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyPackage(string szPackagePath);
213 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiIsProductElevated(string szProductCode, [MarshalAs(UnmanagedType.Bool)] out bool fElevated);
214 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, IntPtr szScriptFilePath, string szTransforms, ushort lgidLanguage);
215 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProduct(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage);
216 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseProductEx(string szPackagePath, string szScriptFilePath, string szTransforms, ushort lgidLanguage, uint dwPlatform, uint dwReserved);
217 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiAdvertiseScript(string szScriptFile, uint dwFlags, IntPtr phRegData, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems);
218 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProcessAdvertiseScript(string szScriptFile, string szIconFolder, IntPtr hRegData, [MarshalAs(UnmanagedType.Bool)] bool fShortcuts, [MarshalAs(UnmanagedType.Bool)] bool fRemoveItems);
219 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoFromScript(string szScriptFile, StringBuilder lpProductBuf39, out ushort plgidLanguage, out uint pdwVersion, StringBuilder lpNameBuf, ref uint cchNameBuf, StringBuilder lpPackageBuf, ref uint cchPackageBuf);
220 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiProvideAssembly(string szAssemblyName, string szAppContext, uint dwInstallMode, uint dwAssemblyInfo, StringBuilder lpPathBuf, ref uint cchPathBuf);
221 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingComponent(string szProduct, string szComponent, int eInstallState);
222 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiInstallMissingFile(string szProduct, string szFile);
223 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern int MsiLocateComponent(string szComponent, StringBuilder lpPathBuf, ref uint cchBuf);
224 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductProperty(int hProduct, string szProperty, StringBuilder lpValueBuf, ref uint cchValueBuf);
225 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetFeatureInfo(int hProduct, string szFeature, out uint lpAttributes, StringBuilder lpTitleBuf, ref uint cchTitleBuf, StringBuilder lpHelpBuf, ref uint cchHelpBuf);
226 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiVerifyDiskSpace(int hInstall);
227 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost);
228 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes);
229 +
230 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiRemovePatches(string szPatchList, string szProductCode, int eUninstallType, string szPropertyList);
231 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDetermineApplicablePatches(string szProductPackagePath, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1), In, Out] MsiPatchSequenceData[] pPatchInfo);
232 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiDeterminePatchSequence(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint cPatchInfo, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3), In, Out] MsiPatchSequenceData[] pPatchInfo);
233 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiApplyMultiplePatches(string szPatchPackages, string szProductCode, string szPropertiesList);
234 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumPatchesEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwFilter, uint dwIndex, StringBuilder szPatchCode, StringBuilder szTargetProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwTargetProductContext, StringBuilder szTargetUserSid, ref uint pcchTargetUserSid);
235 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchInfoEx(string szPatchCode, string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue);
236 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEnumProductsEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwIndex, StringBuilder szInstalledProductCode, [MarshalAs(UnmanagedType.I4)] out UserContexts pdwInstalledContext, StringBuilder szSid, ref uint pcchSid);
237 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetProductInfoEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szProperty, StringBuilder lpValue, ref uint pcchValue);
238 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryFeatureStateEx(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szFeature, out int pdwState);
239 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiQueryComponentState(string szProductCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, string szComponent, out int pdwState);
240 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiExtractPatchXMLData(string szPatchPath, uint dwReserved, StringBuilder szXMLData, ref uint pcchXMLData);
241 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumSources(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, StringBuilder szSource, ref uint pcchSource);
242 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddSourceEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource, uint dwIndex);
243 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearSource(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szSource);
244 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearAllEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions);
245 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListForceResolutionEx(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions);
246 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListGetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, StringBuilder szValue, ref uint pcchValue);
247 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListSetInfo(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, string szProperty, string szValue);
248 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListEnumMediaDisks(string szProductCodeOrPatchCode, string szUserSID, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwIndex, out uint pdwDiskId, StringBuilder szVolumeLabel, ref uint pcchVolumeLabel, StringBuilder szDiskPrompt, ref uint pcchDiskPrompt);
249 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListAddMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskId, string szVolumeLabel, string szDiskPrompt);
250 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSourceListClearMediaDisk(string szProductCodeOrPatchCode, string szUserSid, [MarshalAs(UnmanagedType.I4)] UserContexts dwContext, uint dwOptions, uint dwDiskID);
251 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiNotifySidChange(string szOldSid, string szNewSid);
252 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiSetExternalUIRecord([MarshalAs(UnmanagedType.FunctionPtr)] NativeExternalUIRecordHandler puiHandler, uint dwMessageFilter, IntPtr pvContext, out NativeExternalUIRecordHandler ppuiPrevHandler);
253 +
254 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiGetPatchFileList(string szProductCode, string szPatchList, out uint cFiles, out IntPtr phFileRecords);
255 +
256 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiBeginTransaction(string szTransactionName, int dwTransactionAttributes, out int hTransaction, out IntPtr phChangeOfOwnerEvent);
257 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiEndTransaction(int dwTransactionState);
258 +[DllImport("msi.dll", CharSet=CharSet.Unicode)] internal static extern uint MsiJoinTransaction(int hTransaction, int dwTransactionAttributes, out IntPtr phChangeOfOwnerEvent);
259 +
260 +[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="LoadLibraryExW")] internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
261 +[DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FreeLibrary(IntPtr hModule);
262 +[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr FindResourceEx(IntPtr hModule, IntPtr type, IntPtr name, ushort langId);
263 +[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LoadResource(IntPtr hModule, IntPtr lpResourceInfo);
264 +[DllImport("kernel32.dll", SetLastError=true)] internal static extern IntPtr LockResource(IntPtr resourceData);
265 +[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, EntryPoint="FormatMessageW")] internal static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
266 +[DllImport("kernel32.dll", SetLastError=true)] internal static extern int WaitForSingleObject(IntPtr handle, int milliseconds);
267 +
268 +[DllImport("ole32.dll")] internal static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IntPtr stgPriority, uint grfMode, IntPtr snbExclude, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen);
269 +[DllImport("ole32.dll")] internal static extern int StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string wcsName, uint grfMode, uint reserved, [MarshalAs(UnmanagedType.Interface)] out IStorage stgOpen);
270 +
271 +[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="MessageBoxW")] internal static extern MessageResult MessageBox(IntPtr hWnd, string lpText, string lpCaption, [MarshalAs(UnmanagedType.U4)] int uType);
272 +
273 + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
274 + internal struct MsiPatchSequenceData
275 + {
276 + public string szPatchData;
277 + public int ePatchDataType;
278 + public int dwOrder;
279 + public uint dwStatus;
280 + }
281 +
282 + internal class MsiHandle : SafeHandle
283 + {
284 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
285 + public MsiHandle(IntPtr handle, bool ownsHandle)
286 + : base(handle, ownsHandle)
287 + {
288 + }
289 +
290 + public override bool IsInvalid
291 + {
292 + get
293 + {
294 + return this.handle == IntPtr.Zero;
295 + }
296 + }
297 +
298 + public static implicit operator IntPtr(MsiHandle msiHandle)
299 + {
300 + return msiHandle.handle;
301 + }
302 +
303 + protected override bool ReleaseHandle()
304 + {
305 + return RemotableNativeMethods.MsiCloseHandle((int) this.handle) == 0;
306 + }
307 + }
308 +}
309 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/PatchInstallation.cs new
+413
@@ -0,0 +1,413 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// The Patch object represents a unique instance of a patch that has been
13 + /// registered or applied.
14 + /// </summary>
15 + public class PatchInstallation : Installation
16 + {
17 + /// <summary>
18 + /// Enumerates all patch installations on the system.
19 + /// </summary>
20 + /// <returns>Enumeration of patch objects.</returns>
21 + /// <remarks><p>
22 + /// Win32 MSI API:
23 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatches.asp">MsiEnumPatches</a>
24 + /// </p></remarks>
25 + public static IEnumerable<PatchInstallation> AllPatches
26 + {
27 + get
28 + {
29 + return PatchInstallation.GetPatches(null, null, null, UserContexts.All, PatchStates.All);
30 + }
31 + }
32 +
33 + /// <summary>
34 + /// Enumerates patch installations based on certain criteria.
35 + /// </summary>
36 + /// <param name="patchCode">PatchCode (GUID) of the patch to be enumerated. Only
37 + /// instances of patches within the scope of the context specified by the
38 + /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be
39 + /// enumerated. This parameter may be set to null to enumerate all patches in the specified
40 + /// context.</param>
41 + /// <param name="targetProductCode">ProductCode (GUID) product whose patches are to be
42 + /// enumerated. If non-null, patch enumeration is restricted to instances of this product
43 + /// within the specified context. If null, the patches for all products under the specified
44 + /// context are enumerated.</param>
45 + /// <param name="userSid">Specifies a security identifier (SID) that restricts the context
46 + /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts
47 + /// enumeration to the current user or any user in the system. The special SID string
48 + /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter
49 + /// can be set to null to restrict the enumeration scope to the current user. When
50 + /// <paramref name="userSid"/> must be null.</param>
51 + /// <param name="context">Specifies the user context.</param>
52 + /// <param name="states">The <see cref="PatchStates"/> of patches to return.</param>
53 + /// <remarks><p>
54 + /// Win32 MSI APIs:
55 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumpatchesex.asp">MsiEnumPatchesEx</a>
56 + /// </p></remarks>
57 + public static IEnumerable<PatchInstallation> GetPatches(
58 + string patchCode,
59 + string targetProductCode,
60 + string userSid,
61 + UserContexts context,
62 + PatchStates states)
63 + {
64 + StringBuilder buf = new StringBuilder(40);
65 + StringBuilder targetProductBuf = new StringBuilder(40);
66 + UserContexts targetContext;
67 + StringBuilder targetSidBuf = new StringBuilder(40);
68 + for (uint i = 0; ; i++)
69 + {
70 + uint targetSidBufSize = (uint) targetSidBuf.Capacity;
71 + uint ret = NativeMethods.MsiEnumPatchesEx(
72 + targetProductCode,
73 + userSid,
74 + context,
75 + (uint) states,
76 + i,
77 + buf,
78 + targetProductBuf,
79 + out targetContext,
80 + targetSidBuf,
81 + ref targetSidBufSize);
82 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
83 + {
84 + targetSidBuf.Capacity = (int) ++targetSidBufSize;
85 + ret = NativeMethods.MsiEnumPatchesEx(
86 + targetProductCode,
87 + userSid,
88 + context,
89 + (uint) states,
90 + i,
91 + buf,
92 + targetProductBuf,
93 + out targetContext,
94 + targetSidBuf,
95 + ref targetSidBufSize);
96 + }
97 +
98 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
99 + {
100 + break;
101 + }
102 +
103 + if (ret != 0)
104 + {
105 + throw InstallerException.ExceptionFromReturnCode(ret);
106 + }
107 +
108 + string thisPatchCode = buf.ToString();
109 + if (patchCode == null || patchCode == thisPatchCode)
110 + {
111 + yield return new PatchInstallation(
112 + buf.ToString(),
113 + targetProductBuf.ToString(),
114 + targetSidBuf.ToString(),
115 + targetContext);
116 + }
117 + }
118 + }
119 +
120 + private string productCode;
121 +
122 + /// <summary>
123 + /// Creates a new object for accessing information about a patch installation on the current system.
124 + /// </summary>
125 + /// <param name="patchCode">Patch code (GUID) of the patch.</param>
126 + /// <param name="productCode">ProductCode (GUID) the patch has been applied to.
127 + /// This parameter may be null for patches that are registered only and not yet
128 + /// applied to any product.</param>
129 + /// <remarks><p>
130 + /// All available user contexts will be queried.
131 + /// </p></remarks>
132 + public PatchInstallation(string patchCode, string productCode)
133 + : this(patchCode, productCode, null, UserContexts.All)
134 + {
135 + }
136 +
137 + /// <summary>
138 + /// Creates a new object for accessing information about a patch installation on the current system.
139 + /// </summary>
140 + /// <param name="patchCode">Registered patch code (GUID) of the patch.</param>
141 + /// <param name="productCode">ProductCode (GUID) the patch has been applied to.
142 + /// This parameter may be null for patches that are registered only and not yet
143 + /// applied to any product.</param>
144 + /// <param name="userSid">The specific user, when working in a user context. This
145 + /// parameter may be null to indicate the current user. The parameter must be null
146 + /// when working in a machine context.</param>
147 + /// <param name="context">The user context. The calling process must have administrative
148 + /// privileges to get information for a product installed for a user other than the
149 + /// current user.</param>
150 + /// <remarks><p>
151 + /// If the <paramref name="productCode"/> is null, the Patch object may
152 + /// only be used to read and update the patch's SourceList information.
153 + /// </p></remarks>
154 + public PatchInstallation(string patchCode, string productCode, string userSid, UserContexts context)
155 + : base(patchCode, userSid, context)
156 + {
157 + if (String.IsNullOrEmpty(patchCode))
158 + {
159 + throw new ArgumentNullException("patchCode");
160 + }
161 +
162 + this.productCode = productCode;
163 + }
164 +
165 + /// <summary>
166 + /// Gets the patch code (GUID) of the patch.
167 + /// </summary>
168 + public string PatchCode
169 + {
170 + get
171 + {
172 + return this.InstallationCode;
173 + }
174 + }
175 +
176 + /// <summary>
177 + /// Gets the ProductCode (GUID) of the product.
178 + /// </summary>
179 + public string ProductCode
180 + {
181 + get
182 + {
183 + return this.productCode;
184 + }
185 + }
186 +
187 + /// <summary>
188 + /// Gets a value indicating whether this patch is currently installed.
189 + /// </summary>
190 + public override bool IsInstalled
191 + {
192 + get
193 + {
194 + return (this.State & PatchStates.Applied) != 0;
195 + }
196 + }
197 +
198 + /// <summary>
199 + /// Gets a value indicating whether this patch is marked as obsolte.
200 + /// </summary>
201 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Obsoleted")]
202 + public bool IsObsoleted
203 + {
204 + get
205 + {
206 + return (this.State & PatchStates.Obsoleted) != 0;
207 + }
208 + }
209 +
210 + /// <summary>
211 + /// Gets a value indicating whether this patch is present but has been
212 + /// superseded by a more recent installed patch.
213 + /// </summary>
214 + public bool IsSuperseded
215 + {
216 + get
217 + {
218 + return (this.State & PatchStates.Superseded) != 0;
219 + }
220 + }
221 +
222 + internal override int InstallationType
223 + {
224 + get
225 + {
226 + const int MSICODE_PATCH = 0x40000000;
227 + return MSICODE_PATCH;
228 + }
229 + }
230 +
231 + /// <summary>
232 + /// Gets the installation state of this instance of the patch.
233 + /// </summary>
234 + /// <exception cref="ArgumentException">An unknown patch was requested</exception>
235 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
236 + public PatchStates State
237 + {
238 + get
239 + {
240 + string stateString = this["State"];
241 + return (PatchStates) Int32.Parse(stateString, CultureInfo.InvariantCulture.NumberFormat);
242 + }
243 + }
244 +
245 + /// <summary>
246 + /// Gets the cached patch file that the product uses.
247 + /// </summary>
248 + public string LocalPackage
249 + {
250 + get
251 + {
252 + return this["LocalPackage"];
253 + }
254 + }
255 +
256 + /// <summary>
257 + /// Gets the set of patch transforms that the last patch
258 + /// installation applied to the product.
259 + /// </summary>
260 + /// <remarks><p>
261 + /// This value may not be available for per-user, non-managed applications
262 + /// if the user is not logged on.
263 + /// </p></remarks>
264 + public string Transforms
265 + {
266 + get
267 + {
268 + // TODO: convert to IList<string>?
269 + return this["Transforms"];
270 + }
271 + }
272 +
273 + /// <summary>
274 + /// Gets the date and time when the patch is applied to the product.
275 + /// </summary>
276 + public DateTime InstallDate
277 + {
278 + get
279 + {
280 + try
281 + {
282 + return DateTime.ParseExact(
283 + this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture);
284 + }
285 + catch (FormatException)
286 + {
287 + return DateTime.MinValue;
288 + }
289 + }
290 + }
291 +
292 + /// <summary>
293 + /// True patch is marked as possible to uninstall from the product.
294 + /// </summary>
295 + /// <remarks><p>
296 + /// Even if this property is true, the installer can still block the
297 + /// uninstallation if this patch is required by another patch that
298 + /// cannot be uninstalled.
299 + /// </p></remarks>
300 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uninstallable")]
301 + public bool Uninstallable
302 + {
303 + get
304 + {
305 + return this["Uninstallable"] == "1";
306 + }
307 + }
308 +
309 + /// <summary>
310 + /// Get the registered display name for the patch.
311 + /// </summary>
312 + public string DisplayName
313 + {
314 + get
315 + {
316 + return this["DisplayName"];
317 + }
318 + }
319 +
320 + /// <summary>
321 + /// Gets the registered support information URL for the patch.
322 + /// </summary>
323 + public Uri MoreInfoUrl
324 + {
325 + get
326 + {
327 + string value = this["MoreInfoURL"];
328 + if (!String.IsNullOrEmpty(value))
329 + {
330 + try
331 + {
332 + return new Uri(value);
333 + }
334 + catch (UriFormatException) { }
335 + }
336 +
337 + return null;
338 + }
339 + }
340 +
341 + /// <summary>
342 + /// Gets information about a specific patch installation.
343 + /// </summary>
344 + /// <param name="propertyName">The property being retrieved; see remarks for valid properties.</param>
345 + /// <returns>The property value, or an empty string if the property is not set for the patch.</returns>
346 + /// <exception cref="ArgumentOutOfRangeException">An unknown patch or property was requested</exception>
347 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
348 + /// <remarks><p>
349 + /// Win32 MSI APIs:
350 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfo.asp">MsiGetPatchInfo</a>,
351 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetpatchinfoex.asp">MsiGetPatchInfoEx</a>
352 + /// </p></remarks>
353 + public override string this[string propertyName]
354 + {
355 + get
356 + {
357 + StringBuilder buf = new StringBuilder("");
358 + uint bufSize = 0;
359 + uint ret;
360 +
361 + if (this.Context == UserContexts.UserManaged ||
362 + this.Context == UserContexts.UserUnmanaged ||
363 + this.Context == UserContexts.Machine)
364 + {
365 + ret = NativeMethods.MsiGetPatchInfoEx(
366 + this.PatchCode,
367 + this.ProductCode,
368 + this.UserSid,
369 + this.Context,
370 + propertyName,
371 + buf,
372 + ref bufSize);
373 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
374 + {
375 + buf.Capacity = (int) ++bufSize;
376 + ret = NativeMethods.MsiGetPatchInfoEx(
377 + this.PatchCode,
378 + this.ProductCode,
379 + this.UserSid,
380 + this.Context,
381 + propertyName,
382 + buf,
383 + ref bufSize);
384 + }
385 + }
386 + else
387 + {
388 + ret = NativeMethods.MsiGetPatchInfo(
389 + this.PatchCode,
390 + propertyName,
391 + buf,
392 + ref bufSize);
393 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
394 + {
395 + buf.Capacity = (int) ++bufSize;
396 + ret = NativeMethods.MsiGetPatchInfo(
397 + this.PatchCode,
398 + propertyName,
399 + buf,
400 + ref bufSize);
401 + }
402 + }
403 +
404 + if (ret != 0)
405 + {
406 + return null;
407 + }
408 +
409 + return buf.ToString();
410 + }
411 + }
412 + }
413 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ProductInstallation.cs new
+801
@@ -0,0 +1,801 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// Represents a unique instance of a product that
13 + /// is either advertised, installed or unknown.
14 + /// </summary>
15 + public class ProductInstallation : Installation
16 + {
17 + /// <summary>
18 + /// Gets the set of all products with a specified upgrade code. This method lists the
19 + /// currently installed and advertised products that have the specified UpgradeCode
20 + /// property in their Property table.
21 + /// </summary>
22 + /// <param name="upgradeCode">Upgrade code of related products</param>
23 + /// <returns>Enumeration of product codes</returns>
24 + /// <remarks><p>
25 + /// Win32 MSI API:
26 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumrelatedproducts.asp">MsiEnumRelatedProducts</a>
27 + /// </p></remarks>
28 + public static IEnumerable<ProductInstallation> GetRelatedProducts(string upgradeCode)
29 + {
30 + StringBuilder buf = new StringBuilder(40);
31 + for (uint i = 0; true; i++)
32 + {
33 + uint ret = NativeMethods.MsiEnumRelatedProducts(upgradeCode, 0, i, buf);
34 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
35 + if (ret != 0)
36 + {
37 + throw InstallerException.ExceptionFromReturnCode(ret);
38 + }
39 + yield return new ProductInstallation(buf.ToString());
40 + }
41 + }
42 +
43 + /// <summary>
44 + /// Enumerates all product installations on the system.
45 + /// </summary>
46 + /// <returns>An enumeration of product objects.</returns>
47 + /// <remarks><p>
48 + /// Win32 MSI API:
49 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproducts.asp">MsiEnumProducts</a>,
50 + /// </p></remarks>
51 + public static IEnumerable<ProductInstallation> AllProducts
52 + {
53 + get
54 + {
55 + return GetProducts(null, null, UserContexts.All);
56 + }
57 + }
58 +
59 + /// <summary>
60 + /// Enumerates product installations based on certain criteria.
61 + /// </summary>
62 + /// <param name="productCode">ProductCode (GUID) of the product instances to be enumerated. Only
63 + /// instances of products within the scope of the context specified by the
64 + /// <paramref name="userSid"/> and <paramref name="context"/> parameters will be
65 + /// enumerated. This parameter may be set to null to enumerate all products in the specified
66 + /// context.</param>
67 + /// <param name="userSid">Specifies a security identifier (SID) that restricts the context
68 + /// of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts
69 + /// enumeration to the current user or any user in the system. The special SID string
70 + /// s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter
71 + /// can be set to null to restrict the enumeration scope to the current user. When
72 + /// <paramref name="context"/> is set to the machine context only,
73 + /// <paramref name="userSid"/> must be null.</param>
74 + /// <param name="context">Specifies the user context.</param>
75 + /// <returns>An enumeration of product objects for enumerated product instances.</returns>
76 + /// <remarks><p>
77 + /// Win32 MSI API:
78 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumproductsex.asp">MsiEnumProductsEx</a>
79 + /// </p></remarks>
80 + public static IEnumerable<ProductInstallation> GetProducts(
81 + string productCode, string userSid, UserContexts context)
82 + {
83 + StringBuilder buf = new StringBuilder(40);
84 + UserContexts targetContext;
85 + StringBuilder targetSidBuf = new StringBuilder(40);
86 + for (uint i = 0; ; i++)
87 + {
88 + uint targetSidBufSize = (uint) targetSidBuf.Capacity;
89 + uint ret = NativeMethods.MsiEnumProductsEx(
90 + productCode,
91 + userSid,
92 + context,
93 + i,
94 + buf,
95 + out targetContext,
96 + targetSidBuf,
97 + ref targetSidBufSize);
98 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
99 + {
100 + targetSidBuf.Capacity = (int) ++targetSidBufSize;
101 + ret = NativeMethods.MsiEnumProductsEx(
102 + productCode,
103 + userSid,
104 + context,
105 + i,
106 + buf,
107 + out targetContext,
108 + targetSidBuf,
109 + ref targetSidBufSize);
110 + }
111 +
112 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
113 + {
114 + break;
115 + }
116 +
117 + if (ret != 0)
118 + {
119 + throw InstallerException.ExceptionFromReturnCode(ret);
120 + }
121 +
122 + yield return new ProductInstallation(
123 + buf.ToString(),
124 + targetSidBuf.ToString(),
125 + targetContext);
126 + }
127 + }
128 +
129 + private IDictionary<string, string> properties;
130 +
131 + /// <summary>
132 + /// Creates a new object for accessing information about a product installation on the current system.
133 + /// </summary>
134 + /// <param name="productCode">ProductCode (GUID) of the product.</param>
135 + /// <remarks><p>
136 + /// All available user contexts will be queried.
137 + /// </p></remarks>
138 + public ProductInstallation(string productCode)
139 + : this(productCode, null, UserContexts.All)
140 + {
141 + }
142 +
143 + /// <summary>
144 + /// Creates a new object for accessing information about a product installation on the current system.
145 + /// </summary>
146 + /// <param name="productCode">ProductCode (GUID) of the product.</param>
147 + /// <param name="userSid">The specific user, when working in a user context. This
148 + /// parameter may be null to indicate the current user. The parameter must be null
149 + /// when working in a machine context.</param>
150 + /// <param name="context">The user context. The calling process must have administrative
151 + /// privileges to get information for a product installed for a user other than the
152 + /// current user.</param>
153 + public ProductInstallation(string productCode, string userSid, UserContexts context)
154 + : base(productCode, userSid, context)
155 + {
156 + if (String.IsNullOrEmpty(productCode))
157 + {
158 + throw new ArgumentNullException("productCode");
159 + }
160 + }
161 +
162 + internal ProductInstallation(IDictionary<string, string> properties)
163 + : base(properties["ProductCode"], null, UserContexts.None)
164 + {
165 + this.properties = properties;
166 + }
167 +
168 + /// <summary>
169 + /// Gets the set of published features for the product.
170 + /// </summary>
171 + /// <returns>Enumeration of published features for the product.</returns>
172 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
173 + /// <remarks><p>
174 + /// Because features are not ordered, any new feature has an arbitrary index, meaning
175 + /// this property can return features in any order.
176 + /// </p><p>
177 + /// Win32 MSI API:
178 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumfeatures.asp">MsiEnumFeatures</a>
179 + /// </p></remarks>
180 + public IEnumerable<FeatureInstallation> Features
181 + {
182 + get
183 + {
184 + StringBuilder buf = new StringBuilder(256);
185 + for (uint i = 0; ; i++)
186 + {
187 + uint ret = NativeMethods.MsiEnumFeatures(this.ProductCode, i, buf, null);
188 +
189 + if (ret != 0)
190 + {
191 + break;
192 + }
193 +
194 + yield return new FeatureInstallation(buf.ToString(), this.ProductCode);
195 + }
196 + }
197 + }
198 +
199 + /// <summary>
200 + /// Gets the ProductCode (GUID) of the product.
201 + /// </summary>
202 + public string ProductCode
203 + {
204 + get { return this.InstallationCode; }
205 + }
206 +
207 + /// <summary>
208 + /// Gets a value indicating whether this product is installed on the current system.
209 + /// </summary>
210 + public override bool IsInstalled
211 + {
212 + get
213 + {
214 + return (this.State == InstallState.Default);
215 + }
216 + }
217 +
218 + /// <summary>
219 + /// Gets a value indicating whether this product is advertised on the current system.
220 + /// </summary>
221 + public bool IsAdvertised
222 + {
223 + get
224 + {
225 + return (this.State == InstallState.Advertised);
226 + }
227 + }
228 +
229 + /// <summary>
230 + /// Checks whether the product is installed with elevated privileges. An application is called
231 + /// a "managed application" if elevated (system) privileges are used to install the application.
232 + /// </summary>
233 + /// <returns>True if the product is elevated; false otherwise</returns>
234 + /// <remarks><p>
235 + /// Note that this property does not take into account policies such as AlwaysInstallElevated,
236 + /// but verifies that the local system owns the product's registry data.
237 + /// </p></remarks>
238 + public bool IsElevated
239 + {
240 + get
241 + {
242 + bool isElevated;
243 + uint ret = NativeMethods.MsiIsProductElevated(this.ProductCode, out isElevated);
244 + if (ret != 0)
245 + {
246 + throw InstallerException.ExceptionFromReturnCode(ret);
247 + }
248 + return isElevated;
249 + }
250 + }
251 +
252 + /// <summary>
253 + /// Gets the source list of this product installation.
254 + /// </summary>
255 + public override SourceList SourceList
256 + {
257 + get
258 + {
259 + return this.properties == null ? base.SourceList : null;
260 + }
261 + }
262 +
263 + internal InstallState State
264 + {
265 + get
266 + {
267 + if (this.properties != null)
268 + {
269 + return InstallState.Unknown;
270 + }
271 + else
272 + {
273 + int installState = NativeMethods.MsiQueryProductState(this.ProductCode);
274 + return (InstallState) installState;
275 + }
276 + }
277 + }
278 +
279 + internal override int InstallationType
280 + {
281 + get
282 + {
283 + const int MSICODE_PRODUCT = 0x00000000;
284 + return MSICODE_PRODUCT;
285 + }
286 + }
287 +
288 + /// <summary>
289 + /// The support link.
290 + /// </summary>
291 + public string HelpLink
292 + {
293 + get
294 + {
295 + return this["HelpLink"];
296 + }
297 + }
298 +
299 + /// <summary>
300 + /// The support telephone.
301 + /// </summary>
302 + public string HelpTelephone
303 + {
304 + get
305 + {
306 + return this["HelpTelephone"];
307 + }
308 + }
309 +
310 + /// <summary>
311 + /// Date and time the product was installed.
312 + /// </summary>
313 + public DateTime InstallDate
314 + {
315 + get
316 + {
317 + try
318 + {
319 + return DateTime.ParseExact(
320 + this["InstallDate"], "yyyyMMdd", CultureInfo.InvariantCulture);
321 + }
322 + catch (FormatException)
323 + {
324 + return DateTime.MinValue;
325 + }
326 + }
327 + }
328 +
329 + /// <summary>
330 + /// The installed product name.
331 + /// </summary>
332 + public string ProductName
333 + {
334 + get
335 + {
336 + return this["InstalledProductName"];
337 + }
338 + }
339 +
340 + /// <summary>
341 + /// The installation location.
342 + /// </summary>
343 + public string InstallLocation
344 + {
345 + get
346 + {
347 + return this["InstallLocation"];
348 + }
349 + }
350 +
351 + /// <summary>
352 + /// The installation source.
353 + /// </summary>
354 + public string InstallSource
355 + {
356 + get
357 + {
358 + return this["InstallSource"];
359 + }
360 + }
361 +
362 + /// <summary>
363 + /// The local cached package.
364 + /// </summary>
365 + public string LocalPackage
366 + {
367 + get
368 + {
369 + return this["LocalPackage"];
370 + }
371 + }
372 +
373 + /// <summary>
374 + /// The publisher.
375 + /// </summary>
376 + public string Publisher
377 + {
378 + get
379 + {
380 + return this["Publisher"];
381 + }
382 + }
383 +
384 + /// <summary>
385 + /// URL about information.
386 + /// </summary>
387 + public Uri UrlInfoAbout
388 + {
389 + get
390 + {
391 + string value = this["URLInfoAbout"];
392 + if (!String.IsNullOrEmpty(value))
393 + {
394 + try
395 + {
396 + return new Uri(value);
397 + }
398 + catch (UriFormatException) { }
399 + }
400 +
401 + return null;
402 + }
403 + }
404 +
405 + /// <summary>
406 + /// The URL update information.
407 + /// </summary>
408 + public Uri UrlUpdateInfo
409 + {
410 + get
411 + {
412 + string value = this["URLUpdateInfo"];
413 + if (!String.IsNullOrEmpty(value))
414 + {
415 + try
416 + {
417 + return new Uri(value);
418 + }
419 + catch (UriFormatException) { }
420 + }
421 +
422 + return null;
423 + }
424 + }
425 +
426 + /// <summary>
427 + /// The product version.
428 + /// </summary>
429 + public Version ProductVersion
430 + {
431 + get
432 + {
433 + string ver = this["VersionString"];
434 + return ProductInstallation.ParseVersion(ver);
435 + }
436 + }
437 +
438 + /// <summary>
439 + /// The product identifier.
440 + /// </summary>
441 + /// <remarks><p>
442 + /// For more information, see
443 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/productid.asp">ProductID</a>
444 + /// </p></remarks>
445 + public string ProductId
446 + {
447 + get
448 + {
449 + return this["ProductID"];
450 + }
451 + }
452 +
453 + /// <summary>
454 + /// The company that is registered to use the product.
455 + /// </summary>
456 + public string RegCompany
457 + {
458 + get
459 + {
460 + return this["RegCompany"];
461 + }
462 + }
463 +
464 + /// <summary>
465 + /// The owner who is registered to use the product.
466 + /// </summary>
467 + public string RegOwner
468 + {
469 + get
470 + {
471 + return this["RegOwner"];
472 + }
473 + }
474 +
475 + /// <summary>
476 + /// Transforms.
477 + /// </summary>
478 + public string AdvertisedTransforms
479 + {
480 + get
481 + {
482 + return this["Transforms"];
483 + }
484 + }
485 +
486 + /// <summary>
487 + /// Product language.
488 + /// </summary>
489 + public string AdvertisedLanguage
490 + {
491 + get
492 + {
493 + return this["Language"];
494 + }
495 + }
496 +
497 + /// <summary>
498 + /// Human readable product name.
499 + /// </summary>
500 + public string AdvertisedProductName
501 + {
502 + get
503 + {
504 + return this["ProductName"];
505 + }
506 + }
507 +
508 + /// <summary>
509 + /// True if the product is advertised per-machine;
510 + /// false if it is per-user or not advertised.
511 + /// </summary>
512 + public bool AdvertisedPerMachine
513 + {
514 + get
515 + {
516 + return this["AssignmentType"] == "1";
517 + }
518 + }
519 +
520 + /// <summary>
521 + /// Identifier of the package that a product is installed from.
522 + /// </summary>
523 + public string AdvertisedPackageCode
524 + {
525 + get
526 + {
527 + return this["PackageCode"];
528 + }
529 + }
530 +
531 + /// <summary>
532 + /// Version of the advertised product.
533 + /// </summary>
534 + public Version AdvertisedVersion
535 + {
536 + get
537 + {
538 + string ver = this["Version"];
539 + return ProductInstallation.ParseVersion(ver);
540 + }
541 + }
542 +
543 + /// <summary>
544 + /// Primary icon for the package.
545 + /// </summary>
546 + public string AdvertisedProductIcon
547 + {
548 + get
549 + {
550 + return this["ProductIcon"];
551 + }
552 + }
553 +
554 + /// <summary>
555 + /// Name of the installation package for the advertised product.
556 + /// </summary>
557 + public string AdvertisedPackageName
558 + {
559 + get
560 + {
561 + return this["PackageName"];
562 + }
563 + }
564 +
565 + /// <summary>
566 + /// True if the advertised product can be serviced by
567 + /// non-administrators without elevation.
568 + /// </summary>
569 + public bool PrivilegedPatchingAuthorized
570 + {
571 + get
572 + {
573 + return this["AuthorizedLUAApp"] == "1";
574 + }
575 + }
576 +
577 + /// <summary>
578 + /// Gets information about an installation of a product.
579 + /// </summary>
580 + /// <param name="propertyName">Name of the property being retrieved.</param>
581 + /// <exception cref="ArgumentOutOfRangeException">An unknown product or property was requested</exception>
582 + /// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
583 + /// <remarks><p>
584 + /// Win32 MSI APIs:
585 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfo.asp">MsiGetProductInfo</a>,
586 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductinfoex.asp">MsiGetProductInfoEx</a>
587 + /// </p></remarks>
588 + public override string this[string propertyName]
589 + {
590 + get
591 + {
592 + if (this.properties != null)
593 + {
594 + string value = null;
595 + this.properties.TryGetValue(propertyName, out value);
596 + return value;
597 + }
598 + else
599 + {
600 + StringBuilder buf = new StringBuilder(40);
601 + uint bufSize = (uint) buf.Capacity;
602 + uint ret;
603 +
604 + if (this.Context == UserContexts.UserManaged ||
605 + this.Context == UserContexts.UserUnmanaged ||
606 + this.Context == UserContexts.Machine)
607 + {
608 + ret = NativeMethods.MsiGetProductInfoEx(
609 + this.ProductCode,
610 + this.UserSid,
611 + this.Context,
612 + propertyName,
613 + buf,
614 + ref bufSize);
615 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
616 + {
617 + buf.Capacity = (int) ++bufSize;
618 + ret = NativeMethods.MsiGetProductInfoEx(
619 + this.ProductCode,
620 + this.UserSid,
621 + this.Context,
622 + propertyName,
623 + buf,
624 + ref bufSize);
625 + }
626 + }
627 + else
628 + {
629 + ret = NativeMethods.MsiGetProductInfo(
630 + this.ProductCode,
631 + propertyName,
632 + buf,
633 + ref bufSize);
634 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
635 + {
636 + buf.Capacity = (int) ++bufSize;
637 + ret = NativeMethods.MsiGetProductInfo(
638 + this.ProductCode,
639 + propertyName,
640 + buf,
641 + ref bufSize);
642 + }
643 + }
644 +
645 + if (ret != 0)
646 + {
647 + return null;
648 + }
649 +
650 + return buf.ToString();
651 + }
652 + }
653 + }
654 +
655 + /// <summary>
656 + /// Gets the installed state for a product feature.
657 + /// </summary>
658 + /// <param name="feature">The feature being queried; identifier from the
659 + /// Feature table</param>
660 + /// <returns>Installation state of the feature for the product instance: either
661 + /// <see cref="InstallState.Local"/>, <see cref="InstallState.Source"/>,
662 + /// or <see cref="InstallState.Advertised"/>.</returns>
663 + /// <remarks><p>
664 + /// Win32 MSI APIs:
665 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestate.asp">MsiQueryFeatureState</a>,
666 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiqueryfeaturestateex.asp">MsiQueryFeatureStateEx</a>
667 + /// </p></remarks>
668 + public InstallState GetFeatureState(string feature)
669 + {
670 + if (this.properties != null)
671 + {
672 + return InstallState.Unknown;
673 + }
674 + else
675 + {
676 + int installState;
677 + uint ret = NativeMethods.MsiQueryFeatureStateEx(
678 + this.ProductCode,
679 + this.UserSid,
680 + this.Context,
681 + feature,
682 + out installState);
683 + if (ret != 0)
684 + {
685 + throw InstallerException.ExceptionFromReturnCode(ret);
686 + }
687 + return (InstallState) installState;
688 + }
689 + }
690 +
691 + /// <summary>
692 + /// Gets the installed state for a product component.
693 + /// </summary>
694 + /// <param name="component">The component being queried; GUID of the component
695 + /// as found in the ComponentId column of the Component table.</param>
696 + /// <returns>Installation state of the component for the product instance: either
697 + /// <see cref="InstallState.Local"/> or <see cref="InstallState.Source"/>.</returns>
698 + /// <remarks><p>
699 + /// Win32 MSI API:
700 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiquerycomponnetstate.asp">MsiQueryComponentState</a>
701 + /// </p></remarks>
702 + public InstallState GetComponentState(string component)
703 + {
704 + if (this.properties != null)
705 + {
706 + return InstallState.Unknown;
707 + }
708 + else
709 + {
710 + int installState;
711 + uint ret = NativeMethods.MsiQueryComponentState(
712 + this.ProductCode,
713 + this.UserSid,
714 + this.Context,
715 + component,
716 + out installState);
717 + if (ret != 0)
718 + {
719 + throw InstallerException.ExceptionFromReturnCode(ret);
720 + }
721 + return (InstallState) installState;
722 + }
723 + }
724 +
725 + /// <summary>
726 + /// Obtains and stores the user information and product ID from an installation wizard.
727 + /// </summary>
728 + /// <remarks><p>
729 + /// This method is typically called by an application during the first run of the application. The application
730 + /// first gets the <see cref="ProductInstallation.ProductId"/> or <see cref="ProductInstallation.RegOwner"/>.
731 + /// If those properties are missing, the application calls CollectUserInfo.
732 + /// CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects
733 + /// user information. Upon completion of the sequence, user information is registered. Since this API requires
734 + /// an authored user interface, the user interface level should be set to full by calling
735 + /// <see cref="Installer.SetInternalUI(InstallUIOptions)"/> as <see cref="InstallUIOptions.Full"/>.
736 + /// </p><p>
737 + /// The CollectUserInfo method invokes a FirstRun dialog from the product installation database.
738 + /// </p><p>
739 + /// Win32 MSI API:
740 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicollectuserinfo.asp">MsiCollectUserInfo</a>
741 + /// </p></remarks>
742 + public void CollectUserInfo()
743 + {
744 + if (this.properties == null)
745 + {
746 + uint ret = NativeMethods.MsiCollectUserInfo(this.InstallationCode);
747 + if (ret != 0)
748 + {
749 + throw InstallerException.ExceptionFromReturnCode(ret);
750 + }
751 + }
752 + }
753 +
754 + /// <summary>
755 + /// Some products might write some invalid/nonstandard version strings to the registry.
756 + /// This method tries to get the best data it can.
757 + /// </summary>
758 + /// <param name="ver">Version string retrieved from the registry.</param>
759 + /// <returns>Version object, or null if the version string is completely invalid.</returns>
760 + private static Version ParseVersion(string ver)
761 + {
762 + if (ver != null)
763 + {
764 + int dotCount = 0;
765 + for (int i = 0; i < ver.Length; i++)
766 + {
767 + char c = ver[i];
768 + if (c == '.') dotCount++;
769 + else if (!Char.IsDigit(c))
770 + {
771 + ver = ver.Substring(0, i);
772 + break;
773 + }
774 + }
775 +
776 + if (ver.Length > 0)
777 + {
778 + if (dotCount == 0)
779 + {
780 + ver = ver + ".0";
781 + }
782 + else if (dotCount > 3)
783 + {
784 + string[] verSplit = ver.Split('.');
785 + ver = String.Join(".", verSplit, 0, 4);
786 + }
787 +
788 + try
789 + {
790 + return new Version(ver);
791 + }
792 + catch (ArgumentException)
793 + {
794 + }
795 + }
796 + }
797 +
798 + return null;
799 + }
800 + }
801 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Record.cs new
+1048
@@ -0,0 +1,1048 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using System.Runtime.InteropServices;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + /// <summary>
13 + /// The Record object is a container for holding and transferring a variable number of values.
14 + /// Fields within the record are numerically indexed and can contain strings, integers, streams,
15 + /// and null values. Record fields are indexed starting with 1. Field 0 is a special format field.
16 + /// </summary>
17 + /// <remarks><p>
18 + /// Most methods on the Record class have overloads that allow using either a number
19 + /// or a name to designate a field. However note that field names only exist when the
20 + /// Record is directly returned from a query on a database. For other records, attempting
21 + /// to access a field by name will result in an InvalidOperationException.
22 + /// </p></remarks>
23 + public class Record : InstallerHandle
24 + {
25 + private View view;
26 + private bool isFormatStringInvalid;
27 +
28 + /// <summary>
29 + /// IsFormatStringInvalid is set from several View methods that invalidate the FormatString
30 + /// and used to determine behavior during Record.ToString().
31 + /// </summary>
32 + internal protected bool IsFormatStringInvalid
33 + {
34 + set { this.isFormatStringInvalid = value; }
35 +
36 + get { return this.isFormatStringInvalid; }
37 + }
38 +
39 + /// <summary>
40 + /// Creates a new record object with the requested number of fields.
41 + /// </summary>
42 + /// <param name="fieldCount">Required number of fields, which may be 0.
43 + /// The maximum number of fields in a record is limited to 65535.</param>
44 + /// <remarks><p>
45 + /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
46 + /// It is best that the handle be closed manually as soon as it is no longer
47 + /// needed, as leaving lots of unused handles open can degrade performance.
48 + /// </p><p>
49 + /// Win32 MSI API:
50 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
51 + /// </p></remarks>
52 + public Record(int fieldCount)
53 + : this((IntPtr) RemotableNativeMethods.MsiCreateRecord((uint) fieldCount, 0), true, (View) null)
54 + {
55 + }
56 +
57 + /// <summary>
58 + /// Creates a new record object, providing values for an arbitrary number of fields.
59 + /// </summary>
60 + /// <param name="fields">The values of the record fields. The parameters should be of type Int16, Int32 or String</param>
61 + /// <remarks><p>
62 + /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
63 + /// It is best that the handle be closed manually as soon as it is no longer
64 + /// needed, as leaving lots of unused handles open can degrade performance.
65 + /// </p><p>
66 + /// Win32 MSI API:
67 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msicreaterecord.asp">MsiCreateRecord</a>
68 + /// </p></remarks>
69 + public Record(params object[] fields)
70 + : this(fields.Length)
71 + {
72 + for (int i = 0; i < fields.Length; i++)
73 + {
74 + this[i + 1] = fields[i];
75 + }
76 + }
77 +
78 + internal Record(IntPtr handle, bool ownsHandle, View view)
79 + : base(handle, ownsHandle)
80 + {
81 + this.view = view;
82 + }
83 +
84 + /// <summary>
85 + /// Gets the number of fields in a record.
86 + /// </summary>
87 + /// <remarks><p>
88 + /// Win32 MSI API:
89 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetfieldcount.asp">MsiRecordGetFieldCount</a>
90 + /// </p></remarks>
91 + public int FieldCount
92 + {
93 + get
94 + {
95 + return (int) RemotableNativeMethods.MsiRecordGetFieldCount((int) this.Handle);
96 + }
97 + }
98 +
99 + /// <summary>
100 + /// Gets or sets field 0 of the Record, which is the format string.
101 + /// </summary>
102 + public string FormatString
103 + {
104 + get { return this.GetString(0); }
105 + set { this.SetString(0, value); }
106 + }
107 +
108 + /// <summary>
109 + /// Gets or sets a record field value.
110 + /// </summary>
111 + /// <param name="fieldName">Specifies the name of the field of the Record to get or set.</param>
112 + /// <exception cref="ArgumentOutOfRangeException">The name does not match any known field of the Record.</exception>
113 + /// <remarks><p>
114 + /// When getting a field, the type of the object returned depends on the type of the Record field.
115 + /// The object will be one of: Int16, Int32, String, Stream, or null.
116 + /// </p><p>
117 + /// When setting a field, the type of the object provided will be converted to match the View
118 + /// query that returned the record, or if Record was not returned from a view then the type of
119 + /// the object provided will determine the type of the Record field. The object should be one of:
120 + /// Int16, Int32, String, Stream, or null.
121 + /// </p></remarks>
122 + public object this[string fieldName]
123 + {
124 + get
125 + {
126 + int field = this.FindColumn(fieldName);
127 + return this[field];
128 + }
129 +
130 + set
131 + {
132 + int field = this.FindColumn(fieldName);
133 + this[field] = value;
134 + }
135 + }
136 +
137 + /// <summary>
138 + /// Gets or sets a record field value.
139 + /// </summary>
140 + /// <param name="field">Specifies the field of the Record to get or set.</param>
141 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
142 + /// number of fields in the Record.</exception>
143 + /// <remarks><p>
144 + /// Record fields are indexed starting with 1. Field 0 is a special format field.
145 + /// </p><p>
146 + /// When getting a field, the type of the object returned depends on the type of the Record field.
147 + /// The object will be one of: Int16, Int32, String, Stream, or null. If the Record was returned
148 + /// from a View, the type will match that of the field from the View query. Otherwise, the type
149 + /// will match the type of the last value set for the field.
150 + /// </p><p>
151 + /// When setting a field, the type of the object provided will be converted to match the View
152 + /// query that returned the Record, or if Record was not returned from a View then the type of
153 + /// the object provided will determine the type of the Record field. The object should be one of:
154 + /// Int16, Int32, String, Stream, or null.
155 + /// </p><p>
156 + /// The type-specific getters and setters are slightly more efficient than this property, since
157 + /// they don't have to do the extra work to infer the value's type every time.
158 + /// </p><p>
159 + /// Win32 MSI APIs:
160 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>,
161 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>,
162 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>,
163 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a>
164 + /// </p></remarks>
165 + public object this[int field]
166 + {
167 + get
168 + {
169 + if (field == 0)
170 + {
171 + return this.GetString(0);
172 + }
173 + else
174 + {
175 + Type valueType = null;
176 + if (this.view != null)
177 + {
178 + this.CheckRange(field);
179 +
180 + valueType = this.view.Columns[field - 1].Type;
181 + }
182 +
183 + if (valueType == null || valueType == typeof(String))
184 + {
185 + return this.GetString(field);
186 + }
187 + else if (valueType == typeof(Stream))
188 + {
189 + return this.IsNull(field) ? null : new RecordStream(this, field);
190 + }
191 + else
192 + {
193 + int? value = this.GetNullableInteger(field);
194 + return value.HasValue ? (object) value.Value : null;
195 + }
196 + }
197 + }
198 +
199 + set
200 + {
201 + if (field == 0)
202 + {
203 + if (value == null)
204 + {
205 + value = String.Empty;
206 + }
207 +
208 + this.SetString(0, value.ToString());
209 + }
210 + else if (value == null)
211 + {
212 + this.SetNullableInteger(field, null);
213 + }
214 + else
215 + {
216 + Type valueType = value.GetType();
217 + if (valueType == typeof(Int32) || valueType == typeof(Int16))
218 + {
219 + this.SetInteger(field, (int) value);
220 + }
221 + else if (valueType.IsSubclassOf(typeof(Stream)))
222 + {
223 + this.SetStream(field, (Stream) value);
224 + }
225 + else
226 + {
227 + this.SetString(field, value.ToString());
228 + }
229 + }
230 + }
231 + }
232 +
233 + /// <summary>
234 + /// Creates a new Record object from an integer record handle.
235 + /// </summary>
236 + /// <remarks><p>
237 + /// This method is only provided for interop purposes. A Record object
238 + /// should normally be obtained by calling <see cref="WixToolset.Dtf.WindowsInstaller.View.Fetch"/>
239 + /// other methods.
240 + /// <p>The handle will be closed when this object is disposed or finalized.</p>
241 + /// </p></remarks>
242 + /// <param name="handle">Integer record handle</param>
243 + /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
244 + public static Record FromHandle(IntPtr handle, bool ownsHandle)
245 + {
246 + return new Record(handle, ownsHandle, (View) null);
247 + }
248 +
249 + /// <summary>
250 + /// Sets all fields in a record to null.
251 + /// </summary>
252 + /// <remarks><p>
253 + /// Win32 MSI API:
254 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordcleardata.asp">MsiRecordClearData</a>
255 + /// </p></remarks>
256 + public void Clear()
257 + {
258 + uint ret = RemotableNativeMethods.MsiRecordClearData((int) this.Handle);
259 + if (ret != 0)
260 + {
261 + throw InstallerException.ExceptionFromReturnCode(ret);
262 + }
263 + }
264 +
265 + /// <summary>
266 + /// Reports whether a record field is null.
267 + /// </summary>
268 + /// <param name="field">Specifies the field to check.</param>
269 + /// <returns>True if the field is null, false otherwise.</returns>
270 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
271 + /// number of fields in the Record.</exception>
272 + /// <remarks><p>
273 + /// Win32 MSI API:
274 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordisnull.asp">MsiRecordIsNull</a>
275 + /// </p></remarks>
276 + public bool IsNull(int field)
277 + {
278 + this.CheckRange(field);
279 + return RemotableNativeMethods.MsiRecordIsNull((int) this.Handle, (uint) field);
280 + }
281 +
282 + /// <summary>
283 + /// Reports whether a record field is null.
284 + /// </summary>
285 + /// <param name="fieldName">Specifies the field to check.</param>
286 + /// <returns>True if the field is null, false otherwise.</returns>
287 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
288 + /// of the named fields in the Record.</exception>
289 + public bool IsNull(string fieldName)
290 + {
291 + int field = this.FindColumn(fieldName);
292 + return this.IsNull(field);
293 + }
294 +
295 + /// <summary>
296 + /// Gets the length of a record field. The count does not include the terminating null.
297 + /// </summary>
298 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
299 + /// number of fields in the Record.</exception>
300 + /// <remarks><p>
301 + /// The returned data size is 0 if the field is null, non-existent,
302 + /// or an internal object pointer. The method also returns 0 if the handle is not a valid
303 + /// Record handle.
304 + /// </p><p>
305 + /// If the data is in integer format, the property returns 2 or 4.
306 + /// </p><p>
307 + /// If the data is in string format, the property returns the character count
308 + /// (not including the NULL terminator).
309 + /// </p><p>
310 + /// If the data is in stream format, the property returns the byte count.
311 + /// </p><p>
312 + /// Win32 MSI API:
313 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecorddatasize.asp">MsiRecordDataSize</a>
314 + /// </p></remarks>
315 + public int GetDataSize(int field)
316 + {
317 + this.CheckRange(field);
318 + return (int) RemotableNativeMethods.MsiRecordDataSize((int) this.Handle, (uint) field);
319 + }
320 +
321 + /// <summary>
322 + /// Gets the length of a record field. The count does not include the terminating null.
323 + /// </summary>
324 + /// <param name="fieldName">Specifies the field to check.</param>
325 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
326 + /// of the named fields in the Record.</exception>
327 + /// <remarks><p>The returned data size is 0 if the field is null, non-existent,
328 + /// or an internal object pointer. The method also returns 0 if the handle is not a valid
329 + /// Record handle.
330 + /// </p><p>
331 + /// If the data is in integer format, the property returns 2 or 4.
332 + /// </p><p>
333 + /// If the data is in string format, the property returns the character count
334 + /// (not including the NULL terminator).
335 + /// </p><p>
336 + /// If the data is in stream format, the property returns the byte count.
337 + /// </p></remarks>
338 + public int GetDataSize(string fieldName)
339 + {
340 + int field = this.FindColumn(fieldName);
341 + return this.GetDataSize(field);
342 + }
343 +
344 + /// <summary>
345 + /// Gets a field value as an integer.
346 + /// </summary>
347 + /// <param name="field">Specifies the field to retrieve.</param>
348 + /// <returns>Integer value of the field, or 0 if the field is null.</returns>
349 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
350 + /// number of fields in the Record.</exception>
351 + /// <remarks><p>
352 + /// Win32 MSI API:
353 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>
354 + /// </p></remarks>
355 + /// <seealso cref="GetNullableInteger(int)"/>
356 + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")]
357 + public int GetInteger(int field)
358 + {
359 + this.CheckRange(field);
360 +
361 + int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field);
362 + if (value == Int32.MinValue) // MSI_NULL_INTEGER
363 + {
364 + return 0;
365 + }
366 + return value;
367 + }
368 +
369 + /// <summary>
370 + /// Gets a field value as an integer.
371 + /// </summary>
372 + /// <param name="fieldName">Specifies the field to retrieve.</param>
373 + /// <returns>Integer value of the field, or 0 if the field is null.</returns>
374 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
375 + /// of the named fields in the Record.</exception>
376 + /// <seealso cref="GetNullableInteger(string)"/>
377 + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "integer")]
378 + public int GetInteger(string fieldName)
379 + {
380 + int field = this.FindColumn(fieldName);
381 + return this.GetInteger(field);
382 + }
383 +
384 + /// <summary>
385 + /// Gets a field value as an integer.
386 + /// </summary>
387 + /// <param name="field">Specifies the field to retrieve.</param>
388 + /// <returns>Integer value of the field, or null if the field is null.</returns>
389 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
390 + /// number of fields in the Record.</exception>
391 + /// <remarks><p>
392 + /// Win32 MSI API:
393 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetinteger.asp">MsiRecordGetInteger</a>
394 + /// </p></remarks>
395 + /// <seealso cref="GetInteger(int)"/>
396 + public int? GetNullableInteger(int field)
397 + {
398 + this.CheckRange(field);
399 +
400 + int value = RemotableNativeMethods.MsiRecordGetInteger((int) this.Handle, (uint) field);
401 + if (value == Int32.MinValue) // MSI_NULL_INTEGER
402 + {
403 + return null;
404 + }
405 + return value;
406 + }
407 +
408 + /// <summary>
409 + /// Gets a field value as an integer.
410 + /// </summary>
411 + /// <param name="fieldName">Specifies the field to retrieve.</param>
412 + /// <returns>Integer value of the field, or null if the field is null.</returns>
413 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
414 + /// of the named fields in the Record.</exception>
415 + /// <seealso cref="GetInteger(string)"/>
416 + public int? GetNullableInteger(string fieldName)
417 + {
418 + int field = this.FindColumn(fieldName);
419 + return this.GetInteger(field);
420 + }
421 +
422 + /// <summary>
423 + /// Sets the value of a field to an integer.
424 + /// </summary>
425 + /// <param name="field">Specifies the field to set.</param>
426 + /// <param name="value">new value of the field</param>
427 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
428 + /// number of fields in the Record.</exception>
429 + /// <remarks><p>
430 + /// Win32 MSI API:
431 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>
432 + /// </p></remarks>
433 + /// <seealso cref="SetNullableInteger(int,int?)"/>
434 + public void SetInteger(int field, int value)
435 + {
436 + this.CheckRange(field);
437 +
438 + uint ret = RemotableNativeMethods.MsiRecordSetInteger((int) this.Handle, (uint) field, value);
439 + if (ret != 0)
440 + {
441 + throw InstallerException.ExceptionFromReturnCode(ret);
442 + }
443 + }
444 +
445 + /// <summary>
446 + /// Sets the value of a field to an integer.
447 + /// </summary>
448 + /// <param name="fieldName">Specifies the field to set.</param>
449 + /// <param name="value">new value of the field</param>
450 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
451 + /// of the named fields in the Record.</exception>
452 + /// <seealso cref="SetNullableInteger(string,int?)"/>
453 + public void SetInteger(string fieldName, int value)
454 + {
455 + int field = this.FindColumn(fieldName);
456 + this.SetInteger(field, value);
457 + }
458 +
459 + /// <summary>
460 + /// Sets the value of a field to a nullable integer.
461 + /// </summary>
462 + /// <param name="field">Specifies the field to set.</param>
463 + /// <param name="value">new value of the field</param>
464 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
465 + /// number of fields in the Record.</exception>
466 + /// <remarks><p>
467 + /// Win32 MSI API:
468 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetinteger.asp">MsiRecordSetInteger</a>
469 + /// </p></remarks>
470 + /// <seealso cref="SetInteger(int,int)"/>
471 + public void SetNullableInteger(int field, int? value)
472 + {
473 + this.CheckRange(field);
474 +
475 + uint ret = RemotableNativeMethods.MsiRecordSetInteger(
476 + (int) this.Handle,
477 + (uint) field,
478 + value.HasValue ? (int) value : Int32.MinValue);
479 + if (ret != 0)
480 + {
481 + throw InstallerException.ExceptionFromReturnCode(ret);
482 + }
483 + }
484 +
485 + /// <summary>
486 + /// Sets the value of a field to a nullable integer.
487 + /// </summary>
488 + /// <param name="fieldName">Specifies the field to set.</param>
489 + /// <param name="value">new value of the field</param>
490 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
491 + /// of the named fields in the Record.</exception>
492 + /// <seealso cref="SetInteger(string,int)"/>
493 + public void SetNullableInteger(string fieldName, int? value)
494 + {
495 + int field = this.FindColumn(fieldName);
496 + this.SetNullableInteger(field, value);
497 + }
498 +
499 + /// <summary>
500 + /// Gets a field value as a string.
501 + /// </summary>
502 + /// <param name="field">Specifies the field to retrieve.</param>
503 + /// <returns>String value of the field, or an empty string if the field is null.</returns>
504 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
505 + /// number of fields in the Record.</exception>
506 + /// <remarks><p>
507 + /// Win32 MSI API:
508 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordgetstring.asp">MsiRecordGetString</a>
509 + /// </p></remarks>
510 + public string GetString(int field)
511 + {
512 + this.CheckRange(field);
513 +
514 + StringBuilder buf = new StringBuilder(String.Empty);
515 + uint bufSize = 0;
516 + uint ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize);
517 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
518 + {
519 + buf.Capacity = (int) ++bufSize;
520 + ret = RemotableNativeMethods.MsiRecordGetString((int) this.Handle, (uint) field, buf, ref bufSize);
521 + }
522 + if (ret != 0)
523 + {
524 + throw InstallerException.ExceptionFromReturnCode(ret);
525 + }
526 + return buf.ToString();
527 + }
528 +
529 + /// <summary>
530 + /// Gets a field value as a string.
531 + /// </summary>
532 + /// <param name="fieldName">Specifies the field to retrieve.</param>
533 + /// <returns>String value of the field, or an empty string if the field is null.</returns>
534 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
535 + /// of the named fields in the Record.</exception>
536 + public string GetString(string fieldName)
537 + {
538 + int field = this.FindColumn(fieldName);
539 + return this.GetString(field);
540 + }
541 +
542 + /// <summary>
543 + /// Sets the value of a field to a string.
544 + /// </summary>
545 + /// <param name="field">Specifies the field to set.</param>
546 + /// <param name="value">new value of the field</param>
547 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
548 + /// number of fields in the Record.</exception>
549 + /// <remarks><p>
550 + /// Win32 MSI API:
551 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstring.asp">MsiRecordSetString</a>
552 + /// </p></remarks>
553 + public void SetString(int field, string value)
554 + {
555 + this.CheckRange(field);
556 +
557 + if (value == null)
558 + {
559 + value = String.Empty;
560 + }
561 +
562 + uint ret = RemotableNativeMethods.MsiRecordSetString((int) this.Handle, (uint) field, value);
563 + if (ret != 0)
564 + {
565 + throw InstallerException.ExceptionFromReturnCode(ret);
566 + }
567 +
568 + // If we set the FormatString manually, then it should be valid again
569 + if (field == 0)
570 + {
571 + this.IsFormatStringInvalid = false;
572 + }
573 + }
574 +
575 + /// <summary>
576 + /// Sets the value of a field to a string.
577 + /// </summary>
578 + /// <param name="fieldName">Specifies the field to set.</param>
579 + /// <param name="value">new value of the field</param>
580 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
581 + /// of the named fields in the Record.</exception>
582 + public void SetString(string fieldName, string value)
583 + {
584 + int field = this.FindColumn(fieldName);
585 + this.SetString(field, value);
586 + }
587 +
588 + /// <summary>
589 + /// Reads a record stream field into a file.
590 + /// </summary>
591 + /// <param name="field">Specifies the field of the Record to get.</param>
592 + /// <param name="filePath">Specifies the path to the file to contain the stream.</param>
593 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
594 + /// number of fields in the Record.</exception>
595 + /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open
596 + /// in read-write mode, or from a database without an associated file path</exception>
597 + /// <remarks><p>
598 + /// This method is capable of directly extracting substorages. To do so, first select both the
599 + /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field.
600 + /// However, substorages may only be extracted from a database that is open in read-only mode.
601 + /// </p><p>
602 + /// Win32 MSI API:
603 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a>
604 + /// </p></remarks>
605 + public void GetStream(int field, string filePath)
606 + {
607 + if (String.IsNullOrEmpty(filePath))
608 + {
609 + throw new ArgumentNullException("filePath");
610 + }
611 +
612 + IList<TableInfo> tables = (this.view != null ? this.view.Tables : null);
613 + if (tables != null && tables.Count == 1 && tables[0].Name == "_Storages" && field == this.FindColumn("Data"))
614 + {
615 + if (!this.view.Database.IsReadOnly)
616 + {
617 + throw new NotSupportedException("Database must be opened read-only to support substorage extraction.");
618 + }
619 + else if (this.view.Database.FilePath == null)
620 + {
621 + throw new NotSupportedException("Database must have an associated file path to support substorage extraction.");
622 + }
623 + else if (this.FindColumn("Name") <= 0)
624 + {
625 + throw new NotSupportedException("Name column must be part of the Record in order to extract substorage.");
626 + }
627 + else
628 + {
629 + Record.ExtractSubStorage(this.view.Database.FilePath, this.GetString("Name"), filePath);
630 + }
631 + }
632 + else
633 + {
634 + if (!this.IsNull(field))
635 + {
636 + Stream readStream = null, writeStream = null;
637 + try
638 + {
639 + readStream = new RecordStream(this, field);
640 + writeStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
641 + int count = 512;
642 + byte[] buf = new byte[count];
643 + while (count == buf.Length)
644 + {
645 + if ((count = readStream.Read(buf, 0, buf.Length)) > 0)
646 + {
647 + writeStream.Write(buf, 0, count);
648 + }
649 + }
650 + }
651 + finally
652 + {
653 + if (readStream != null) readStream.Close();
654 + if (writeStream != null) writeStream.Close();
655 + }
656 + }
657 + }
658 + }
659 +
660 + /// <summary>
661 + /// Reads a record stream field into a file.
662 + /// </summary>
663 + /// <param name="fieldName">Specifies the field of the Record to get.</param>
664 + /// <param name="filePath">Specifies the path to the file to contain the stream.</param>
665 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
666 + /// of the named fields in the Record.</exception>
667 + /// <exception cref="NotSupportedException">Attempt to extract a storage from a database open
668 + /// in read-write mode, or from a database without an associated file path</exception>
669 + /// <remarks><p>
670 + /// This method is capable of directly extracting substorages. To do so, first select both the
671 + /// `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field.
672 + /// However, substorages may only be extracted from a database that is open in read-only mode.
673 + /// </p></remarks>
674 + public void GetStream(string fieldName, string filePath)
675 + {
676 + int field = this.FindColumn(fieldName);
677 + this.GetStream(field, filePath);
678 + }
679 +
680 + /// <summary>
681 + /// Gets a record stream field.
682 + /// </summary>
683 + /// <param name="field">Specifies the field of the Record to get.</param>
684 + /// <returns>A Stream that reads the field data.</returns>
685 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
686 + /// number of fields in the Record.</exception>
687 + /// <remarks><p>
688 + /// This method is not capable of reading substorages. To extract a substorage,
689 + /// use <see cref="GetStream(int,string)"/>.
690 + /// </p><p>
691 + /// Win32 MSI API:
692 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordreadstream.asp">MsiRecordReadStream</a>
693 + /// </p></remarks>
694 + public Stream GetStream(int field)
695 + {
696 + this.CheckRange(field);
697 +
698 + return this.IsNull(field) ? null : new RecordStream(this, field);
699 + }
700 +
701 + /// <summary>
702 + /// Gets a record stream field.
703 + /// </summary>
704 + /// <param name="fieldName">Specifies the field of the Record to get.</param>
705 + /// <returns>A Stream that reads the field data.</returns>
706 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
707 + /// of the named fields in the Record.</exception>
708 + /// <remarks><p>
709 + /// This method is not capable of reading substorages. To extract a substorage,
710 + /// use <see cref="GetStream(string,string)"/>.
711 + /// </p></remarks>
712 + public Stream GetStream(string fieldName)
713 + {
714 + int field = this.FindColumn(fieldName);
715 + return this.GetStream(field);
716 + }
717 +
718 + /// <summary>
719 + /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields.
720 + /// </summary>
721 + /// <param name="field">Specifies the field of the Record to set.</param>
722 + /// <param name="filePath">Specifies the path to the file containing the stream.</param>
723 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
724 + /// number of fields in the Record.</exception>
725 + /// <remarks><p>
726 + /// The contents of the specified file are read into a stream object. The stream persists if
727 + /// the Record is inserted into the Database and the Database is committed.
728 + /// </p><p>
729 + /// To reset the stream to its beginning you must pass in null for filePath.
730 + /// Do not pass an empty string, "", to reset the stream.
731 + /// </p><p>
732 + /// Setting a stream with this method is more efficient than setting a field to a
733 + /// FileStream object.
734 + /// </p><p>
735 + /// Win32 MSI API:
736 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a>
737 + /// </p></remarks>
738 + public void SetStream(int field, string filePath)
739 + {
740 + this.CheckRange(field);
741 +
742 + if (String.IsNullOrEmpty(filePath))
743 + {
744 + throw new ArgumentNullException("filePath");
745 + }
746 +
747 + uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, filePath);
748 + if (ret != 0)
749 + {
750 + throw InstallerException.ExceptionFromReturnCode(ret);
751 + }
752 + }
753 +
754 + /// <summary>
755 + /// Sets a record stream field from a file. Stream data cannot be inserted into temporary fields.
756 + /// </summary>
757 + /// <param name="fieldName">Specifies the field name of the Record to set.</param>
758 + /// <param name="filePath">Specifies the path to the file containing the stream.</param>
759 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
760 + /// of the named fields in the Record.</exception>
761 + /// <remarks><p>
762 + /// The contents of the specified file are read into a stream object. The stream persists if
763 + /// the Record is inserted into the Database and the Database is committed.
764 + /// To reset the stream to its beginning you must pass in null for filePath.
765 + /// Do not pass an empty string, "", to reset the stream.
766 + /// </p><p>
767 + /// Setting a stream with this method is more efficient than setting a field to a
768 + /// FileStream object.
769 + /// </p></remarks>
770 + public void SetStream(string fieldName, string filePath)
771 + {
772 + int field = this.FindColumn(fieldName);
773 + this.SetStream(field, filePath);
774 + }
775 +
776 + /// <summary>
777 + /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields.
778 + /// </summary>
779 + /// <param name="field">Specifies the field of the Record to set.</param>
780 + /// <param name="stream">Specifies the stream data.</param>
781 + /// <exception cref="ArgumentOutOfRangeException">The field is less than 0 or greater than the
782 + /// number of fields in the Record.</exception>
783 + /// <remarks><p>
784 + /// The stream persists if the Record is inserted into the Database and the Database is committed.
785 + /// </p><p>
786 + /// Win32 MSI API:
787 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msirecordsetstream.asp">MsiRecordsetStream</a>
788 + /// </p></remarks>
789 + public void SetStream(int field, Stream stream)
790 + {
791 + this.CheckRange(field);
792 +
793 + if (stream == null)
794 + {
795 + uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, null);
796 + if (ret != 0)
797 + {
798 + throw InstallerException.ExceptionFromReturnCode(ret);
799 + }
800 + }
801 + else
802 + {
803 + Stream writeStream = null;
804 + string tempPath = Path.GetTempFileName();
805 + try
806 + {
807 + writeStream = new FileStream(tempPath, FileMode.Truncate, FileAccess.Write);
808 + byte[] buf = new byte[512];
809 + int count;
810 + while ((count = stream.Read(buf, 0, buf.Length)) > 0)
811 + {
812 + writeStream.Write(buf, 0, count);
813 + }
814 + writeStream.Close();
815 + writeStream = null;
816 +
817 + uint ret = RemotableNativeMethods.MsiRecordSetStream((int) this.Handle, (uint) field, tempPath);
818 + if (ret != 0)
819 + {
820 + throw InstallerException.ExceptionFromReturnCode(ret);
821 + }
822 + }
823 + finally
824 + {
825 + if (writeStream != null) writeStream.Close();
826 + if (File.Exists(tempPath))
827 + {
828 + try
829 + {
830 + File.Delete(tempPath);
831 + }
832 + catch (IOException)
833 + {
834 + if (this.view != null)
835 + {
836 + this.view.Database.DeleteOnClose(tempPath);
837 + }
838 + }
839 + }
840 + }
841 + }
842 + }
843 +
844 + /// <summary>
845 + /// Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields.
846 + /// </summary>
847 + /// <param name="fieldName">Specifies the field name of the Record to set.</param>
848 + /// <param name="stream">Specifies the stream data.</param>
849 + /// <exception cref="ArgumentOutOfRangeException">The field name does not match any
850 + /// of the named fields in the Record.</exception>
851 + /// <remarks><p>
852 + /// The stream persists if the Record is inserted into the Database and the Database is committed.
853 + /// </p></remarks>
854 + public void SetStream(string fieldName, Stream stream)
855 + {
856 + int field = this.FindColumn(fieldName);
857 + this.SetStream(field, stream);
858 + }
859 +
860 + /// <summary>
861 + /// Gets a formatted string representation of the Record.
862 + /// </summary>
863 + /// <returns>A formatted string representation of the Record.</returns>
864 + /// <remarks><p>
865 + /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record.
866 + /// </p><p>
867 + /// Win32 MSI API:
868 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
869 + /// </p></remarks>
870 + /// <seealso cref="FormatString"/>
871 + /// <seealso cref="Session.FormatRecord(Record)"/>
872 + public override string ToString()
873 + {
874 + return this.ToString((IFormatProvider) null);
875 + }
876 +
877 + /// <summary>
878 + /// Gets a formatted string representation of the Record, optionally using a Session to format properties.
879 + /// </summary>
880 + /// <param name="provider">an optional Session instance that will be used to lookup any
881 + /// properties in the Record's format string</param>
882 + /// <returns>A formatted string representation of the Record.</returns>
883 + /// <remarks><p>
884 + /// If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record.
885 + /// </p><p>
886 + /// Win32 MSI API:
887 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
888 + /// </p></remarks>
889 + /// <seealso cref="FormatString"/>
890 + /// <seealso cref="Session.FormatRecord(Record)"/>
891 + public string ToString(IFormatProvider provider)
892 + {
893 + if (this.IsFormatStringInvalid) // Format string is invalid
894 + {
895 + // TODO: return all values by default?
896 + return String.Empty;
897 + }
898 +
899 + InstallerHandle session = provider as InstallerHandle;
900 + int sessionHandle = session != null ? (int) session.Handle : 0;
901 + StringBuilder buf = new StringBuilder(String.Empty);
902 + uint bufSize = 1;
903 + uint ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize);
904 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
905 + {
906 + bufSize++;
907 + buf = new StringBuilder((int) bufSize);
908 + ret = RemotableNativeMethods.MsiFormatRecord(sessionHandle, (int) this.Handle, buf, ref bufSize);
909 + }
910 + if (ret != 0)
911 + {
912 + throw InstallerException.ExceptionFromReturnCode(ret);
913 + }
914 + return buf.ToString();
915 + }
916 +
917 + /// <summary>
918 + /// Gets a formatted string representation of the Record.
919 + /// </summary>
920 + /// <param name="format">String to be used to format the data in the Record,
921 + /// instead of the Record's format string.</param>
922 + /// <returns>A formatted string representation of the Record.</returns>
923 + /// <remarks><p>
924 + /// Win32 MSI API:
925 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
926 + /// </p></remarks>
927 + [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " +
928 + "property separately before calling the ToString() override that takes no parameters.")]
929 + public string ToString(string format)
930 + {
931 + return this.ToString(format, null);
932 + }
933 +
934 + /// <summary>
935 + /// Gets a formatted string representation of the Record, optionally using a Session to format properties.
936 + /// </summary>
937 + /// <param name="format">String to be used to format the data in the Record,
938 + /// instead of the Record's format string.</param>
939 + /// <param name="provider">an optional Session instance that will be used to lookup any
940 + /// properties in the Record's format string</param>
941 + /// <returns>A formatted string representation of the Record.</returns>
942 + /// <remarks><p>
943 + /// Win32 MSI API:
944 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
945 + /// </p></remarks>
946 + /// <seealso cref="FormatString"/>
947 + /// <seealso cref="Session.FormatRecord(Record)"/>
948 + [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the FormatString " +
949 + "property separately before calling the ToString() override that takes just a format provider.")]
950 + public string ToString(string format, IFormatProvider provider)
951 + {
952 + if (format == null)
953 + {
954 + return this.ToString(provider);
955 + }
956 + else if (format.Length == 0)
957 + {
958 + return String.Empty;
959 + }
960 + else
961 + {
962 + string savedFormatString = (string) this[0];
963 + try
964 + {
965 + this.FormatString = format;
966 + return this.ToString(provider);
967 + }
968 + finally
969 + {
970 + this.FormatString = savedFormatString;
971 + }
972 + }
973 + }
974 +
975 + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
976 + private static void ExtractSubStorage(string databaseFile, string storageName, string extractFile)
977 + {
978 + IStorage storage;
979 + NativeMethods.STGM openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_DENY_WRITE;
980 + int hr = NativeMethods.StgOpenStorage(databaseFile, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0, out storage);
981 + if (hr != 0)
982 + {
983 + Marshal.ThrowExceptionForHR(hr);
984 + }
985 +
986 + try
987 + {
988 + openMode = NativeMethods.STGM.READ | NativeMethods.STGM.SHARE_EXCLUSIVE;
989 + IStorage subStorage = storage.OpenStorage(storageName, IntPtr.Zero, (uint) openMode, IntPtr.Zero, 0);
990 +
991 + try
992 + {
993 + IStorage newStorage;
994 + openMode = NativeMethods.STGM.CREATE | NativeMethods.STGM.READWRITE | NativeMethods.STGM.SHARE_EXCLUSIVE;
995 + hr = NativeMethods.StgCreateDocfile(extractFile, (uint) openMode, 0, out newStorage);
996 + if (hr != 0)
997 + {
998 + Marshal.ThrowExceptionForHR(hr);
999 + }
1000 +
1001 + try
1002 + {
1003 + subStorage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, newStorage);
1004 +
1005 + newStorage.Commit(0);
1006 + }
1007 + finally
1008 + {
1009 + Marshal.ReleaseComObject(newStorage);
1010 + }
1011 + }
1012 + finally
1013 + {
1014 + Marshal.ReleaseComObject(subStorage);
1015 + }
1016 + }
1017 + finally
1018 + {
1019 + Marshal.ReleaseComObject(storage);
1020 + }
1021 + }
1022 +
1023 + private int FindColumn(string fieldName)
1024 + {
1025 + if (this.view == null)
1026 + {
1027 + throw new InvalidOperationException();
1028 + }
1029 + ColumnCollection columns = this.view.Columns;
1030 + for (int i = 0; i < columns.Count; i++)
1031 + {
1032 + if (columns[i].Name == fieldName)
1033 + {
1034 + return i + 1;
1035 + }
1036 + }
1037 + throw new ArgumentOutOfRangeException("fieldName");
1038 + }
1039 +
1040 + private void CheckRange(int field)
1041 + {
1042 + if (field < 0 || field > this.FieldCount)
1043 + {
1044 + throw new ArgumentOutOfRangeException("field");
1045 + }
1046 + }
1047 + }
1048 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/RecordStream.cs new
+92
@@ -0,0 +1,92 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + internal class RecordStream : Stream
9 + {
10 + private Record record;
11 + private int field;
12 + private long position;
13 +
14 + internal RecordStream(Record record, int field)
15 + : base()
16 + {
17 + this.record = record;
18 + this.field = field;
19 + }
20 +
21 + public override bool CanRead { get { return true; } }
22 + public override bool CanWrite { get { return false; } }
23 + public override bool CanSeek { get { return false; } }
24 +
25 + public override long Length
26 + {
27 + get
28 + {
29 + return this.record.GetDataSize(this.field);
30 + }
31 + }
32 +
33 + public override long Position
34 + {
35 + get
36 + {
37 + return this.position;
38 + }
39 +
40 + set
41 + {
42 + throw new NotSupportedException();
43 + }
44 + }
45 +
46 + public override long Seek(long offset, SeekOrigin origin)
47 + {
48 + throw new NotSupportedException();
49 + }
50 +
51 + public override void SetLength(long value)
52 + {
53 + throw new NotSupportedException();
54 + }
55 +
56 + public override void Flush()
57 + {
58 + throw new NotSupportedException();
59 + }
60 +
61 + public override int Read(byte[] buffer, int offset, int count)
62 + {
63 + if (count > 0)
64 + {
65 + byte[] readBuffer = (offset == 0 ? buffer : new byte[count]);
66 + uint ucount = (uint) count;
67 + uint ret = RemotableNativeMethods.MsiRecordReadStream((int) this.record.Handle, (uint) this.field, buffer, ref ucount);
68 + if (ret != 0)
69 + {
70 + throw InstallerException.ExceptionFromReturnCode(ret);
71 + }
72 + count = (int) ucount;
73 + if (offset > 0)
74 + {
75 + Array.Copy(readBuffer, 0, buffer, offset, count);
76 + }
77 + this.position += count;
78 + }
79 + return count;
80 + }
81 +
82 + public override void Write(byte[] array, int offset, int count)
83 + {
84 + throw new NotSupportedException();
85 + }
86 +
87 + public override string ToString()
88 + {
89 + return "[Binary data]";
90 + }
91 + }
92 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/RemotableNativeMethods.cs new
+1171
@@ -0,0 +1,1171 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Runtime.InteropServices;
8 + using System.Diagnostics.CodeAnalysis;
9 +
10 + /// <summary>
11 + /// Assigns ID numbers to the MSI APIs that are remotable.
12 + /// </summary>
13 + /// <remarks><p>
14 + /// This enumeration MUST stay in sync with the
15 + /// unmanaged equivalent in RemoteMsiSession.h!
16 + /// </p></remarks>
17 + internal enum RemoteMsiFunctionId
18 + {
19 + EndSession = 0,
20 + MsiCloseHandle,
21 + MsiCreateRecord,
22 + MsiDatabaseGetPrimaryKeys,
23 + MsiDatabaseIsTablePersistent,
24 + MsiDatabaseOpenView,
25 + MsiDoAction,
26 + MsiEnumComponentCosts,
27 + MsiEvaluateCondition,
28 + MsiFormatRecord,
29 + MsiGetActiveDatabase,
30 + MsiGetComponentState,
31 + MsiGetFeatureCost,
32 + MsiGetFeatureState,
33 + MsiGetFeatureValidStates,
34 + MsiGetLanguage,
35 + MsiGetLastErrorRecord,
36 + MsiGetMode,
37 + MsiGetProperty,
38 + MsiGetSourcePath,
39 + MsiGetSummaryInformation,
40 + MsiGetTargetPath,
41 + MsiProcessMessage,
42 + MsiRecordClearData,
43 + MsiRecordDataSize,
44 + MsiRecordGetFieldCount,
45 + MsiRecordGetInteger,
46 + MsiRecordGetString,
47 + MsiRecordIsNull,
48 + MsiRecordReadStream,
49 + MsiRecordSetInteger,
50 + MsiRecordSetStream,
51 + MsiRecordSetString,
52 + MsiSequence,
53 + MsiSetComponentState,
54 + MsiSetFeatureAttributes,
55 + MsiSetFeatureState,
56 + MsiSetInstallLevel,
57 + MsiSetMode,
58 + MsiSetProperty,
59 + MsiSetTargetPath,
60 + MsiSummaryInfoGetProperty,
61 + MsiVerifyDiskSpace,
62 + MsiViewExecute,
63 + MsiViewFetch,
64 + MsiViewGetError,
65 + MsiViewGetColumnInfo,
66 + MsiViewModify,
67 + }
68 +
69 + /// <summary>
70 + /// Defines the signature of the native function
71 + /// in SfxCA.dll that implements the remoting call.
72 + /// </summary>
73 + internal delegate void MsiRemoteInvoke(
74 + RemoteMsiFunctionId id,
75 + [MarshalAs(UnmanagedType.SysInt)]
76 + IntPtr request,
77 + [MarshalAs(UnmanagedType.SysInt)]
78 + out IntPtr response);
79 +
80 + /// <summary>
81 + /// Redirects native API calls to either the normal NativeMethods class
82 + /// or to out-of-proc calls via the remoting channel.
83 + /// </summary>
84 + internal static class RemotableNativeMethods
85 + {
86 + private const int MAX_REQUEST_FIELDS = 4;
87 + private static int requestFieldDataOffset;
88 + private static int requestFieldSize;
89 +
90 + [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
91 + private static IntPtr requestBuf;
92 +
93 + private static MsiRemoteInvoke remotingDelegate;
94 +
95 + /// <summary>
96 + /// Checks if the current process is using remoting to access the
97 + /// MSI session and database APIs.
98 + /// </summary>
99 + internal static bool RemotingEnabled
100 + {
101 + get
102 + {
103 + return RemotableNativeMethods.remotingDelegate != null;
104 + }
105 + }
106 +
107 + /// <summary>
108 + /// Sets a delegate that is used to make remote API calls.
109 + /// </summary>
110 + /// <remarks><p>
111 + /// The implementation of this delegate is provided by the
112 + /// custom action host DLL.
113 + /// </p></remarks>
114 + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
115 + internal static MsiRemoteInvoke RemotingDelegate
116 + {
117 + set
118 + {
119 + RemotableNativeMethods.remotingDelegate = value;
120 +
121 + if (value != null && requestBuf == IntPtr.Zero)
122 + {
123 + requestFieldDataOffset = Marshal.SizeOf(typeof(IntPtr));
124 + requestFieldSize = 2 * Marshal.SizeOf(typeof(IntPtr));
125 + RemotableNativeMethods.requestBuf = Marshal.AllocHGlobal(
126 + requestFieldSize * MAX_REQUEST_FIELDS);
127 + }
128 + }
129 + }
130 +
131 + internal static bool IsRemoteHandle(int handle)
132 + {
133 + return (handle & Int32.MinValue) != 0;
134 + }
135 +
136 + internal static int MakeRemoteHandle(int handle)
137 + {
138 + if (handle == 0)
139 + {
140 + return handle;
141 + }
142 +
143 + if (RemotableNativeMethods.IsRemoteHandle(handle))
144 + {
145 + throw new InvalidOperationException("Handle already has the remote bit set.");
146 + }
147 +
148 + return handle ^ Int32.MinValue;
149 + }
150 +
151 + internal static int GetRemoteHandle(int handle)
152 + {
153 + if (handle == 0)
154 + {
155 + return handle;
156 + }
157 +
158 + if (!RemotableNativeMethods.IsRemoteHandle(handle))
159 + {
160 + throw new InvalidOperationException("Handle does not have the remote bit set.");
161 + }
162 +
163 + return handle ^ Int32.MinValue;
164 + }
165 +
166 + private static void ClearData(IntPtr buf)
167 + {
168 + for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
169 + {
170 + Marshal.WriteInt32(buf, (i * requestFieldSize), (int) VarEnum.VT_NULL);
171 + Marshal.WriteIntPtr(buf, (i * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero);
172 + }
173 + }
174 +
175 + private static void WriteInt(IntPtr buf, int field, int value)
176 + {
177 + Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_I4);
178 + Marshal.WriteInt32(buf, (field * requestFieldSize) + requestFieldDataOffset, value);
179 + }
180 +
181 + private static void WriteString(IntPtr buf, int field, string value)
182 + {
183 + if (value == null)
184 + {
185 + Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_NULL);
186 + Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, IntPtr.Zero);
187 + }
188 + else
189 + {
190 + IntPtr stringPtr = Marshal.StringToHGlobalUni(value);
191 + Marshal.WriteInt32(buf, (field * requestFieldSize), (int) VarEnum.VT_LPWSTR);
192 + Marshal.WriteIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset, stringPtr);
193 + }
194 + }
195 +
196 + private static int ReadInt(IntPtr buf, int field)
197 + {
198 + VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
199 + if (vt == VarEnum.VT_EMPTY)
200 + {
201 + return 0;
202 + }
203 + else if (vt != VarEnum.VT_I4 && vt != VarEnum.VT_UI4)
204 + {
205 + throw new InstallerException("Invalid data received from remote MSI function invocation.");
206 + }
207 + return Marshal.ReadInt32(buf, (field * requestFieldSize) + requestFieldDataOffset);
208 + }
209 +
210 + private static void ReadString(IntPtr buf, int field, StringBuilder szBuf, ref uint cchBuf)
211 + {
212 + VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
213 + if (vt == VarEnum.VT_NULL)
214 + {
215 + szBuf.Remove(0, szBuf.Length);
216 + return;
217 + }
218 + else if (vt != VarEnum.VT_LPWSTR)
219 + {
220 + throw new InstallerException("Invalid data received from remote MSI function invocation.");
221 + }
222 +
223 + szBuf.Remove(0, szBuf.Length);
224 + IntPtr strPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
225 + string str = Marshal.PtrToStringUni(strPtr);
226 + if (str != null)
227 + {
228 + szBuf.Append(str);
229 + }
230 + cchBuf = (uint) szBuf.Length;
231 + }
232 +
233 + private static void FreeString(IntPtr buf, int field)
234 + {
235 + IntPtr stringPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
236 + if (stringPtr != IntPtr.Zero)
237 + {
238 + Marshal.FreeHGlobal(stringPtr);
239 + }
240 + }
241 +
242 + private static void ReadStream(IntPtr buf, int field, byte[] sBuf, int count)
243 + {
244 + VarEnum vt = (VarEnum) Marshal.ReadInt32(buf, (field * requestFieldSize));
245 + if (vt != VarEnum.VT_STREAM)
246 + {
247 + throw new InstallerException("Invalid data received from remote MSI function invocation.");
248 + }
249 +
250 + IntPtr sPtr = Marshal.ReadIntPtr(buf, (field * requestFieldSize) + requestFieldDataOffset);
251 + Marshal.Copy(sPtr, sBuf, 0, count);
252 + }
253 +
254 + private static uint MsiFunc_III(RemoteMsiFunctionId id, int in1, int in2, int in3)
255 + {
256 + lock (RemotableNativeMethods.remotingDelegate)
257 + {
258 + ClearData(requestBuf);
259 + WriteInt(requestBuf, 0, in1);
260 + WriteInt(requestBuf, 1, in2);
261 + WriteInt(requestBuf, 2, in3);
262 + IntPtr resp;
263 + remotingDelegate(id, requestBuf, out resp);
264 + return unchecked ((uint) ReadInt(resp, 0));
265 + }
266 + }
267 +
268 + private static uint MsiFunc_IIS(RemoteMsiFunctionId id, int in1, int in2, string in3)
269 + {
270 + lock (RemotableNativeMethods.remotingDelegate)
271 + {
272 + ClearData(requestBuf);
273 + WriteInt(requestBuf, 0, in1);
274 + WriteInt(requestBuf, 1, in2);
275 + WriteString(requestBuf, 2, in3);
276 + IntPtr resp;
277 + remotingDelegate(id, requestBuf, out resp);
278 + FreeString(requestBuf, 2);
279 + return unchecked ((uint) ReadInt(resp, 0));
280 + }
281 + }
282 +
283 + private static uint MsiFunc_ISI(RemoteMsiFunctionId id, int in1, string in2, int in3)
284 + {
285 + lock (RemotableNativeMethods.remotingDelegate)
286 + {
287 + ClearData(requestBuf);
288 + WriteInt(requestBuf, 0, in1);
289 + WriteString(requestBuf, 1, in2);
290 + WriteInt(requestBuf, 2, in3);
291 + IntPtr resp;
292 + remotingDelegate(id, requestBuf, out resp);
293 + FreeString(requestBuf, 2);
294 + return unchecked ((uint) ReadInt(resp, 0));
295 + }
296 + }
297 +
298 + private static uint MsiFunc_ISS(RemoteMsiFunctionId id, int in1, string in2, string in3)
299 + {
300 + lock (RemotableNativeMethods.remotingDelegate)
301 + {
302 + ClearData(requestBuf);
303 + WriteInt(requestBuf, 0, in1);
304 + WriteString(requestBuf, 1, in2);
305 + WriteString(requestBuf, 2, in3);
306 + IntPtr resp;
307 + remotingDelegate(id, requestBuf, out resp);
308 + FreeString(requestBuf, 1);
309 + FreeString(requestBuf, 2);
310 + return unchecked ((uint) ReadInt(resp, 0));
311 + }
312 + }
313 +
314 + private static uint MsiFunc_II_I(RemoteMsiFunctionId id, int in1, int in2, out int out1)
315 + {
316 + lock (RemotableNativeMethods.remotingDelegate)
317 + {
318 + ClearData(requestBuf);
319 + WriteInt(requestBuf, 0, in1);
320 + WriteInt(requestBuf, 1, in2);
321 + IntPtr resp;
322 + remotingDelegate(id, requestBuf, out resp);
323 + uint ret = unchecked ((uint) ReadInt(resp, 0));
324 + out1 = ReadInt(resp, 1);
325 + return ret;
326 + }
327 + }
328 +
329 + private static uint MsiFunc_ISII_I(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, out int out1)
330 + {
331 + lock (RemotableNativeMethods.remotingDelegate)
332 + {
333 + ClearData(requestBuf);
334 + WriteInt(requestBuf, 0, in1);
335 + WriteString(requestBuf, 1, in2);
336 + WriteInt(requestBuf, 2, in3);
337 + WriteInt(requestBuf, 3, in4);
338 + IntPtr resp;
339 + remotingDelegate(id, requestBuf, out resp);
340 + FreeString(requestBuf, 1);
341 + uint ret = unchecked ((uint) ReadInt(resp, 0));
342 + out1 = ReadInt(resp, 1);
343 + return ret;
344 + }
345 + }
346 +
347 + private static uint MsiFunc_IS_II(RemoteMsiFunctionId id, int in1, string in2, out int out1, out int out2)
348 + {
349 + lock (RemotableNativeMethods.remotingDelegate)
350 + {
351 + ClearData(requestBuf);
352 + WriteInt(requestBuf, 0, in1);
353 + WriteString(requestBuf, 1, in2);
354 + IntPtr resp;
355 + remotingDelegate(id, requestBuf, out resp);
356 + FreeString(requestBuf, 1);
357 + uint ret = unchecked ((uint) ReadInt(resp, 0));
358 + out1 = ReadInt(resp, 1);
359 + out2 = ReadInt(resp, 2);
360 + return ret;
361 + }
362 + }
363 +
364 + private static uint MsiFunc_II_S(RemoteMsiFunctionId id, int in1, int in2, StringBuilder out1, ref uint cchOut1)
365 + {
366 + lock (RemotableNativeMethods.remotingDelegate)
367 + {
368 + ClearData(requestBuf);
369 + WriteInt(requestBuf, 0, in1);
370 + WriteInt(requestBuf, 1, in2);
371 + IntPtr resp;
372 + remotingDelegate(id, requestBuf, out resp);
373 + uint ret = unchecked ((uint) ReadInt(resp, 0));
374 + if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
375 + return ret;
376 + }
377 + }
378 +
379 + private static uint MsiFunc_IS_S(RemoteMsiFunctionId id, int in1, string in2, StringBuilder out1, ref uint cchOut1)
380 + {
381 + lock (RemotableNativeMethods.remotingDelegate)
382 + {
383 + ClearData(requestBuf);
384 + WriteInt(requestBuf, 0, in1);
385 + WriteString(requestBuf, 1, in2);
386 + IntPtr resp;
387 + remotingDelegate(id, requestBuf, out resp);
388 + FreeString(requestBuf, 1);
389 + uint ret = unchecked ((uint) ReadInt(resp, 0));
390 + if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
391 + return ret;
392 + }
393 + }
394 +
395 + private static uint MsiFunc_ISII_SII(RemoteMsiFunctionId id, int in1, string in2, int in3, int in4, StringBuilder out1, ref uint cchOut1, out int out2, out int out3)
396 + {
397 + lock (RemotableNativeMethods.remotingDelegate)
398 + {
399 + ClearData(requestBuf);
400 + WriteInt(requestBuf, 0, in1);
401 + WriteString(requestBuf, 1, in2);
402 + WriteInt(requestBuf, 2, in3);
403 + WriteInt(requestBuf, 3, in4);
404 + IntPtr resp;
405 + remotingDelegate(id, requestBuf, out resp);
406 + FreeString(requestBuf, 1);
407 + uint ret = unchecked ((uint) ReadInt(resp, 0));
408 + if (ret == 0) ReadString(resp, 1, out1, ref cchOut1);
409 + out2 = ReadInt(resp, 2);
410 + out3 = ReadInt(resp, 3);
411 + return ret;
412 + }
413 + }
414 +
415 + internal static int MsiProcessMessage(int hInstall, uint eMessageType, int hRecord)
416 + {
417 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
418 + {
419 + return NativeMethods.MsiProcessMessage(hInstall, eMessageType, hRecord);
420 + }
421 + else lock (remotingDelegate)
422 + {
423 + // I don't understand why, but this particular function doesn't work
424 + // when using the static requestBuf -- some data doesn't make it through.
425 + // But it works when a fresh buffer is allocated here every call.
426 + IntPtr buf = Marshal.AllocHGlobal(
427 + requestFieldSize * MAX_REQUEST_FIELDS);
428 + ClearData(buf);
429 + WriteInt(buf, 0, RemotableNativeMethods.GetRemoteHandle(hInstall));
430 + WriteInt(buf, 1, unchecked ((int) eMessageType));
431 + WriteInt(buf, 2, RemotableNativeMethods.GetRemoteHandle(hRecord));
432 + IntPtr resp;
433 + remotingDelegate(RemoteMsiFunctionId.MsiProcessMessage, buf, out resp);
434 + Marshal.FreeHGlobal(buf);
435 + return ReadInt(resp, 0);
436 + }
437 + }
438 +
439 + internal static uint MsiCloseHandle(int hAny)
440 + {
441 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hAny))
442 + return NativeMethods.MsiCloseHandle(hAny);
443 + else
444 + return RemotableNativeMethods.MsiFunc_III(
445 + RemoteMsiFunctionId.MsiCloseHandle, RemotableNativeMethods.GetRemoteHandle(hAny), 0, 0);
446 + }
447 +
448 + internal static uint MsiGetProperty(int hInstall, string szName, StringBuilder szValueBuf, ref uint cchValueBuf)
449 + {
450 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
451 + return NativeMethods.MsiGetProperty(hInstall, szName, szValueBuf, ref cchValueBuf);
452 + else
453 + {
454 + return RemotableNativeMethods.MsiFunc_IS_S(
455 + RemoteMsiFunctionId.MsiGetProperty,
456 + RemotableNativeMethods.GetRemoteHandle(hInstall),
457 + szName,
458 + szValueBuf,
459 + ref cchValueBuf);
460 + }
461 + }
462 +
463 + internal static uint MsiSetProperty(int hInstall, string szName, string szValue)
464 + {
465 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
466 + return NativeMethods.MsiSetProperty(hInstall, szName, szValue);
467 + else
468 + {
469 + return RemotableNativeMethods.MsiFunc_ISS(
470 + RemoteMsiFunctionId.MsiSetProperty,
471 + RemotableNativeMethods.GetRemoteHandle(hInstall),
472 + szName,
473 + szValue);
474 + }
475 + }
476 +
477 + internal static int MsiCreateRecord(uint cParams, int hAny)
478 + {
479 + // When remoting is enabled, we might need to create either a local or
480 + // remote record, depending on the handle it is to have an affinity with.
481 + // If no affinity handle is specified, create a remote record (the 99% case).
482 + if (!RemotingEnabled ||
483 + (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny)))
484 + {
485 + return NativeMethods.MsiCreateRecord(cParams);
486 + }
487 + else
488 + {
489 + int hRecord = unchecked((int)RemotableNativeMethods.MsiFunc_III(
490 + RemoteMsiFunctionId.MsiCreateRecord, (int) cParams, 0, 0));
491 + return RemotableNativeMethods.MakeRemoteHandle(hRecord);
492 + }
493 + }
494 +
495 + internal static uint MsiRecordGetFieldCount(int hRecord)
496 + {
497 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
498 + return NativeMethods.MsiRecordGetFieldCount(hRecord);
499 + else
500 + {
501 + return RemotableNativeMethods.MsiFunc_III(
502 + RemoteMsiFunctionId.MsiRecordGetFieldCount,
503 + RemotableNativeMethods.GetRemoteHandle(hRecord),
504 + 0,
505 + 0);
506 + }
507 + }
508 +
509 + internal static int MsiRecordGetInteger(int hRecord, uint iField)
510 + {
511 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
512 + return NativeMethods.MsiRecordGetInteger(hRecord, iField);
513 + else
514 + {
515 + return unchecked ((int) RemotableNativeMethods.MsiFunc_III(
516 + RemoteMsiFunctionId.MsiRecordGetInteger,
517 + RemotableNativeMethods.GetRemoteHandle(hRecord),
518 + (int) iField,
519 + 0));
520 + }
521 + }
522 +
523 + internal static uint MsiRecordGetString(int hRecord, uint iField, StringBuilder szValueBuf, ref uint cchValueBuf)
524 + {
525 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
526 + {
527 + return NativeMethods.MsiRecordGetString(hRecord, iField, szValueBuf, ref cchValueBuf);
528 + }
529 + else
530 + {
531 + return RemotableNativeMethods.MsiFunc_II_S(
532 + RemoteMsiFunctionId.MsiRecordGetString,
533 + RemotableNativeMethods.GetRemoteHandle(hRecord),
534 + (int) iField,
535 + szValueBuf,
536 + ref cchValueBuf);
537 + }
538 + }
539 +
540 + internal static uint MsiRecordSetInteger(int hRecord, uint iField, int iValue)
541 + {
542 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
543 + return NativeMethods.MsiRecordSetInteger(hRecord, iField, iValue);
544 + else
545 + {
546 + return RemotableNativeMethods.MsiFunc_III(
547 + RemoteMsiFunctionId.MsiRecordSetInteger,
548 + RemotableNativeMethods.GetRemoteHandle(hRecord),
549 + (int) iField,
550 + iValue);
551 + }
552 + }
553 +
554 + internal static uint MsiRecordSetString(int hRecord, uint iField, string szValue)
555 + {
556 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
557 + return NativeMethods.MsiRecordSetString(hRecord, iField, szValue);
558 + else
559 + {
560 + return RemotableNativeMethods.MsiFunc_IIS(
561 + RemoteMsiFunctionId.MsiRecordSetString,
562 + RemotableNativeMethods.GetRemoteHandle(hRecord),
563 + (int) iField,
564 + szValue);
565 + }
566 + }
567 +
568 + internal static int MsiGetActiveDatabase(int hInstall)
569 + {
570 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
571 + return NativeMethods.MsiGetActiveDatabase(hInstall);
572 + else
573 + {
574 + int hDatabase = (int)RemotableNativeMethods.MsiFunc_III(
575 + RemoteMsiFunctionId.MsiGetActiveDatabase,
576 + RemotableNativeMethods.GetRemoteHandle(hInstall),
577 + 0,
578 + 0);
579 + return RemotableNativeMethods.MakeRemoteHandle(hDatabase);
580 + }
581 + }
582 +
583 + internal static uint MsiDatabaseOpenView(int hDatabase, string szQuery, out int hView)
584 + {
585 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
586 + return NativeMethods.MsiDatabaseOpenView(hDatabase, szQuery, out hView);
587 + else
588 + {
589 + uint err = RemotableNativeMethods.MsiFunc_ISII_I(
590 + RemoteMsiFunctionId.MsiDatabaseOpenView,
591 + RemotableNativeMethods.GetRemoteHandle(hDatabase),
592 + szQuery,
593 + 0,
594 + 0,
595 + out hView);
596 + hView = RemotableNativeMethods.MakeRemoteHandle(hView);
597 + return err;
598 + }
599 + }
600 +
601 + internal static uint MsiViewExecute(int hView, int hRecord)
602 + {
603 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
604 + return NativeMethods.MsiViewExecute(hView, hRecord);
605 + else
606 + {
607 + return RemotableNativeMethods.MsiFunc_III(
608 + RemoteMsiFunctionId.MsiViewExecute,
609 + RemotableNativeMethods.GetRemoteHandle(hView),
610 + RemotableNativeMethods.GetRemoteHandle(hRecord),
611 + 0);
612 + }
613 + }
614 +
615 + internal static uint MsiViewFetch(int hView, out int hRecord)
616 + {
617 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
618 + return NativeMethods.MsiViewFetch(hView, out hRecord);
619 + else
620 + {
621 + uint err = RemotableNativeMethods.MsiFunc_II_I(
622 + RemoteMsiFunctionId.MsiViewFetch,
623 + RemotableNativeMethods.GetRemoteHandle(hView),
624 + 0,
625 + out hRecord);
626 + hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
627 + return err;
628 + }
629 + }
630 +
631 + internal static uint MsiViewModify(int hView, int iModifyMode, int hRecord)
632 + {
633 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
634 + return NativeMethods.MsiViewModify(hView, iModifyMode, hRecord);
635 + else
636 + {
637 + return RemotableNativeMethods.MsiFunc_III(
638 + RemoteMsiFunctionId.MsiViewModify,
639 + RemotableNativeMethods.GetRemoteHandle(hView),
640 + iModifyMode,
641 + RemotableNativeMethods.GetRemoteHandle(hRecord));
642 + }
643 + }
644 +
645 + internal static int MsiViewGetError(int hView, StringBuilder szColumnNameBuffer, ref uint cchBuf)
646 + {
647 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
648 + return NativeMethods.MsiViewGetError(hView, szColumnNameBuffer, ref cchBuf);
649 + else
650 + {
651 + return unchecked ((int) RemotableNativeMethods.MsiFunc_II_S(
652 + RemoteMsiFunctionId.MsiViewGetError,
653 + RemotableNativeMethods.GetRemoteHandle(hView),
654 + 0,
655 + szColumnNameBuffer,
656 + ref cchBuf));
657 + }
658 + }
659 +
660 + internal static uint MsiViewGetColumnInfo(int hView, uint eColumnInfo, out int hRecord)
661 + {
662 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hView))
663 + return NativeMethods.MsiViewGetColumnInfo(hView, eColumnInfo, out hRecord);
664 + else
665 + {
666 + uint err = RemotableNativeMethods.MsiFunc_II_I(
667 + RemoteMsiFunctionId.MsiViewGetColumnInfo,
668 + RemotableNativeMethods.GetRemoteHandle(hView),
669 + (int) eColumnInfo,
670 + out hRecord);
671 + hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
672 + return err;
673 + }
674 + }
675 +
676 + internal static uint MsiFormatRecord(int hInstall, int hRecord, StringBuilder szResultBuf, ref uint cchResultBuf)
677 + {
678 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
679 + return NativeMethods.MsiFormatRecord(hInstall, hRecord, szResultBuf, ref cchResultBuf);
680 + else
681 + {
682 + return RemotableNativeMethods.MsiFunc_II_S(
683 + RemoteMsiFunctionId.MsiFormatRecord,
684 + RemotableNativeMethods.GetRemoteHandle(hInstall),
685 + RemotableNativeMethods.GetRemoteHandle(hRecord),
686 + szResultBuf,
687 + ref cchResultBuf);
688 + }
689 + }
690 +
691 + internal static uint MsiRecordClearData(int hRecord)
692 + {
693 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
694 + return NativeMethods.MsiRecordClearData(hRecord);
695 + else
696 + {
697 + return RemotableNativeMethods.MsiFunc_III(
698 + RemoteMsiFunctionId.MsiRecordClearData,
699 + RemotableNativeMethods.GetRemoteHandle(hRecord),
700 + 0,
701 + 0);
702 + }
703 + }
704 +
705 + internal static bool MsiRecordIsNull(int hRecord, uint iField)
706 + {
707 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
708 + return NativeMethods.MsiRecordIsNull(hRecord, iField);
709 + else
710 + {
711 + return 0 != RemotableNativeMethods.MsiFunc_III(
712 + RemoteMsiFunctionId.MsiRecordIsNull,
713 + RemotableNativeMethods.GetRemoteHandle(hRecord),
714 + (int) iField,
715 + 0);
716 + }
717 + }
718 +
719 + internal static uint MsiDatabaseGetPrimaryKeys(int hDatabase, string szTableName, out int hRecord)
720 + {
721 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
722 + return NativeMethods.MsiDatabaseGetPrimaryKeys(hDatabase, szTableName, out hRecord);
723 + else
724 + {
725 + uint err = RemotableNativeMethods.MsiFunc_ISII_I(
726 + RemoteMsiFunctionId.MsiDatabaseGetPrimaryKeys,
727 + RemotableNativeMethods.GetRemoteHandle(hDatabase),
728 + szTableName,
729 + 0,
730 + 0,
731 + out hRecord);
732 + hRecord = RemotableNativeMethods.MakeRemoteHandle(hRecord);
733 + return err;
734 + }
735 + }
736 +
737 + internal static uint MsiDatabaseIsTablePersistent(int hDatabase, string szTableName)
738 + {
739 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
740 + return NativeMethods.MsiDatabaseIsTablePersistent(hDatabase, szTableName);
741 + else
742 + {
743 + return RemotableNativeMethods.MsiFunc_ISI(
744 + RemoteMsiFunctionId.MsiDatabaseIsTablePersistent,
745 + RemotableNativeMethods.GetRemoteHandle(hDatabase),
746 + szTableName,
747 + 0);
748 + }
749 + }
750 +
751 + internal static uint MsiDoAction(int hInstall, string szAction)
752 + {
753 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
754 + return NativeMethods.MsiDoAction(hInstall, szAction);
755 + else
756 + {
757 + return RemotableNativeMethods.MsiFunc_ISI(
758 + RemoteMsiFunctionId.MsiDoAction,
759 + RemotableNativeMethods.GetRemoteHandle(hInstall),
760 + szAction,
761 + 0);
762 + }
763 + }
764 +
765 + internal static uint MsiEnumComponentCosts(int hInstall, string szComponent, uint dwIndex, int iState, StringBuilder lpDriveBuf, ref uint cchDriveBuf, out int iCost, out int iTempCost)
766 + {
767 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
768 + return NativeMethods.MsiEnumComponentCosts(hInstall, szComponent, dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost);
769 + else
770 + {
771 + return RemotableNativeMethods.MsiFunc_ISII_SII(
772 + RemoteMsiFunctionId.MsiEvaluateCondition,
773 + RemotableNativeMethods.GetRemoteHandle(hInstall),
774 + szComponent, (int) dwIndex, iState, lpDriveBuf, ref cchDriveBuf, out iCost, out iTempCost);
775 + }
776 + }
777 +
778 + internal static uint MsiEvaluateCondition(int hInstall, string szCondition)
779 + {
780 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
781 + return NativeMethods.MsiEvaluateCondition(hInstall, szCondition);
782 + else
783 + {
784 + return RemotableNativeMethods.MsiFunc_ISI(
785 + RemoteMsiFunctionId.MsiEvaluateCondition,
786 + RemotableNativeMethods.GetRemoteHandle(hInstall),
787 + szCondition,
788 + 0);
789 + }
790 + }
791 +
792 + internal static uint MsiGetComponentState(int hInstall, string szComponent, out int iInstalled, out int iAction)
793 + {
794 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
795 + return NativeMethods.MsiGetComponentState(hInstall, szComponent, out iInstalled, out iAction);
796 + else
797 + {
798 + return RemotableNativeMethods.MsiFunc_IS_II(
799 + RemoteMsiFunctionId.MsiGetComponentState,
800 + RemotableNativeMethods.GetRemoteHandle(hInstall),
801 + szComponent,
802 + out iInstalled,
803 + out iAction);
804 + }
805 + }
806 +
807 + internal static uint MsiGetFeatureCost(int hInstall, string szFeature, int iCostTree, int iState, out int iCost)
808 + {
809 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
810 + return NativeMethods.MsiGetFeatureCost(hInstall, szFeature, iCostTree, iState, out iCost);
811 + else
812 + {
813 + return RemotableNativeMethods.MsiFunc_ISII_I(
814 + RemoteMsiFunctionId.MsiGetFeatureCost,
815 + RemotableNativeMethods.GetRemoteHandle(hInstall),
816 + szFeature,
817 + iCostTree,
818 + iState,
819 + out iCost);
820 + }
821 + }
822 +
823 + internal static uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction)
824 + {
825 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
826 + return NativeMethods.MsiGetFeatureState(hInstall, szFeature, out iInstalled, out iAction);
827 + else
828 + {
829 + return RemotableNativeMethods.MsiFunc_IS_II(
830 + RemoteMsiFunctionId.MsiGetFeatureState,
831 + RemotableNativeMethods.GetRemoteHandle(hInstall),
832 + szFeature,
833 + out iInstalled,
834 + out iAction);
835 + }
836 + }
837 +
838 + internal static uint MsiGetFeatureValidStates(int hInstall, string szFeature, out uint dwInstalledState)
839 + {
840 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
841 + return NativeMethods.MsiGetFeatureValidStates(hInstall, szFeature, out dwInstalledState);
842 + else
843 + {
844 + int iTemp;
845 + uint ret = RemotableNativeMethods.MsiFunc_ISII_I(
846 + RemoteMsiFunctionId.MsiGetFeatureValidStates,
847 + RemotableNativeMethods.GetRemoteHandle(hInstall),
848 + szFeature,
849 + 0,
850 + 0,
851 + out iTemp);
852 + dwInstalledState = (uint) iTemp;
853 + return ret;
854 + }
855 + }
856 +
857 + internal static int MsiGetLanguage(int hInstall)
858 + {
859 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
860 + return NativeMethods.MsiGetLanguage(hInstall);
861 + else
862 + {
863 + return unchecked((int)RemotableNativeMethods.MsiFunc_III(
864 + RemoteMsiFunctionId.MsiGetLanguage,
865 + RemotableNativeMethods.GetRemoteHandle(hInstall),
866 + 0,
867 + 0));
868 + }
869 + }
870 +
871 + internal static int MsiGetLastErrorRecord(int hAny)
872 + {
873 + // When remoting is enabled, we might need to create either a local or
874 + // remote record, depending on the handle it is to have an affinity with.
875 + // If no affinity handle is specified, create a remote record (the 99% case).
876 + if (!RemotingEnabled ||
877 + (hAny != 0 && !RemotableNativeMethods.IsRemoteHandle(hAny)))
878 + {
879 + return NativeMethods.MsiGetLastErrorRecord();
880 + }
881 + else
882 + {
883 + int hRecord = unchecked((int) RemotableNativeMethods.MsiFunc_III(
884 + RemoteMsiFunctionId.MsiGetLastErrorRecord, 0, 0, 0));
885 + return RemotableNativeMethods.MakeRemoteHandle(hRecord);
886 + }
887 + }
888 +
889 + internal static bool MsiGetMode(int hInstall, uint iRunMode)
890 + {
891 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
892 + return NativeMethods.MsiGetMode(hInstall, iRunMode);
893 + else
894 + {
895 + return 0 != RemotableNativeMethods.MsiFunc_III(
896 + RemoteMsiFunctionId.MsiGetMode,
897 + RemotableNativeMethods.GetRemoteHandle(hInstall),
898 + (int) iRunMode,
899 + 0);
900 + }
901 + }
902 +
903 + internal static uint MsiGetSourcePath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf)
904 + {
905 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
906 + return NativeMethods.MsiGetSourcePath(hInstall, szFolder, szPathBuf, ref cchPathBuf);
907 + else
908 + {
909 + return RemotableNativeMethods.MsiFunc_IS_S(
910 + RemoteMsiFunctionId.MsiGetSourcePath,
911 + RemotableNativeMethods.GetRemoteHandle(hInstall),
912 + szFolder,
913 + szPathBuf,
914 + ref cchPathBuf);
915 + }
916 + }
917 +
918 + internal static uint MsiGetSummaryInformation(int hDatabase, string szDatabasePath, uint uiUpdateCount, out int hSummaryInfo)
919 + {
920 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hDatabase))
921 + return NativeMethods.MsiGetSummaryInformation(hDatabase, szDatabasePath, uiUpdateCount, out hSummaryInfo);
922 + else
923 + {
924 + uint err = RemotableNativeMethods.MsiFunc_ISII_I(
925 + RemoteMsiFunctionId.MsiGetSummaryInformation,
926 + RemotableNativeMethods.GetRemoteHandle(hDatabase),
927 + szDatabasePath,
928 + (int)uiUpdateCount,
929 + 0,
930 + out hSummaryInfo);
931 + hSummaryInfo = RemotableNativeMethods.MakeRemoteHandle(hSummaryInfo);
932 + return err;
933 + }
934 + }
935 +
936 + internal static uint MsiGetTargetPath(int hInstall, string szFolder, StringBuilder szPathBuf, ref uint cchPathBuf)
937 + {
938 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
939 + return NativeMethods.MsiGetTargetPath(hInstall, szFolder, szPathBuf, ref cchPathBuf);
940 + else
941 + {
942 + return RemotableNativeMethods.MsiFunc_IS_S(
943 + RemoteMsiFunctionId.MsiGetTargetPath,
944 + RemotableNativeMethods.GetRemoteHandle(hInstall),
945 + szFolder,
946 + szPathBuf,
947 + ref cchPathBuf);
948 + }
949 + }
950 +
951 + internal static uint MsiRecordDataSize(int hRecord, uint iField)
952 + {
953 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
954 + return NativeMethods.MsiRecordDataSize(hRecord, iField);
955 + else
956 + {
957 + return RemotableNativeMethods.MsiFunc_III(
958 + RemoteMsiFunctionId.MsiRecordDataSize,
959 + RemotableNativeMethods.GetRemoteHandle(hRecord),
960 + (int) iField, 0);
961 + }
962 + }
963 +
964 + internal static uint MsiRecordReadStream(int hRecord, uint iField, byte[] szDataBuf, ref uint cbDataBuf)
965 + {
966 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
967 + {
968 + return NativeMethods.MsiRecordReadStream(hRecord, iField, szDataBuf, ref cbDataBuf);
969 + }
970 + else lock (RemotableNativeMethods.remotingDelegate)
971 + {
972 + ClearData(requestBuf);
973 + unchecked
974 + {
975 + WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hRecord));
976 + WriteInt(requestBuf, 1, (int) iField);
977 + WriteInt(requestBuf, 2, (int) cbDataBuf);
978 + IntPtr resp;
979 + remotingDelegate(RemoteMsiFunctionId.MsiRecordReadStream, requestBuf, out resp);
980 + uint ret = (uint) ReadInt(resp, 0);
981 + if (ret == 0)
982 + {
983 + cbDataBuf = (uint) ReadInt(resp, 2);
984 + if (cbDataBuf > 0)
985 + {
986 + RemotableNativeMethods.ReadStream(resp, 1, szDataBuf, (int) cbDataBuf);
987 + }
988 + }
989 + return ret;
990 + }
991 + }
992 + }
993 +
994 + internal static uint MsiRecordSetStream(int hRecord, uint iField, string szFilePath)
995 + {
996 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hRecord))
997 + return NativeMethods.MsiRecordSetStream(hRecord, iField, szFilePath);
998 + else
999 + {
1000 + return RemotableNativeMethods.MsiFunc_IIS(
1001 + RemoteMsiFunctionId.MsiRecordSetStream,
1002 + RemotableNativeMethods.GetRemoteHandle(hRecord),
1003 + (int) iField,
1004 + szFilePath);
1005 + }
1006 + }
1007 +
1008 + internal static uint MsiSequence(int hInstall, string szTable, int iSequenceMode)
1009 + {
1010 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1011 + return NativeMethods.MsiSequence(hInstall, szTable, iSequenceMode);
1012 + else
1013 + {
1014 + return RemotableNativeMethods.MsiFunc_ISI(
1015 + RemoteMsiFunctionId.MsiSequence,
1016 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1017 + szTable,
1018 + iSequenceMode);
1019 + }
1020 + }
1021 +
1022 + internal static uint MsiSetComponentState(int hInstall, string szComponent, int iState)
1023 + {
1024 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1025 + return NativeMethods.MsiSetComponentState(hInstall, szComponent, iState);
1026 + else
1027 + {
1028 + return RemotableNativeMethods.MsiFunc_ISI(
1029 + RemoteMsiFunctionId.MsiSetComponentState,
1030 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1031 + szComponent,
1032 + iState);
1033 + }
1034 + }
1035 +
1036 + internal static uint MsiSetFeatureAttributes(int hInstall, string szFeature, uint dwAttributes)
1037 + {
1038 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1039 + return NativeMethods.MsiSetFeatureAttributes(hInstall, szFeature, dwAttributes);
1040 + else
1041 + {
1042 + return RemotableNativeMethods.MsiFunc_ISI(
1043 + RemoteMsiFunctionId.MsiSetFeatureAttributes,
1044 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1045 + szFeature,
1046 + (int) dwAttributes);
1047 + }
1048 + }
1049 +
1050 + internal static uint MsiSetFeatureState(int hInstall, string szFeature, int iState)
1051 + {
1052 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1053 + return NativeMethods.MsiSetFeatureState(hInstall, szFeature, iState);
1054 + else
1055 + {
1056 + return RemotableNativeMethods.MsiFunc_ISI(
1057 + RemoteMsiFunctionId.MsiSetFeatureState,
1058 + RemotableNativeMethods.GetRemoteHandle(hInstall), szFeature, iState);
1059 + }
1060 + }
1061 +
1062 + internal static uint MsiSetInstallLevel(int hInstall, int iInstallLevel)
1063 + {
1064 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1065 + return NativeMethods.MsiSetInstallLevel(hInstall, iInstallLevel);
1066 + else
1067 + {
1068 + return RemotableNativeMethods.MsiFunc_III(
1069 + RemoteMsiFunctionId.MsiSetInstallLevel,
1070 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1071 + iInstallLevel,
1072 + 0);
1073 + }
1074 + }
1075 +
1076 + internal static uint MsiSetMode(int hInstall, uint iRunMode, bool fState)
1077 + {
1078 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1079 + return NativeMethods.MsiSetMode(hInstall, iRunMode, fState);
1080 + else
1081 + {
1082 + return RemotableNativeMethods.MsiFunc_III(
1083 + RemoteMsiFunctionId.MsiSetMode,
1084 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1085 + (int) iRunMode,
1086 + fState ? 1 : 0);
1087 + }
1088 + }
1089 +
1090 + internal static uint MsiSetTargetPath(int hInstall, string szFolder, string szFolderPath)
1091 + {
1092 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1093 + return NativeMethods.MsiSetTargetPath(hInstall, szFolder, szFolderPath);
1094 + else
1095 + {
1096 + return RemotableNativeMethods.MsiFunc_ISS(
1097 + RemoteMsiFunctionId.MsiSetTargetPath,
1098 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1099 + szFolder,
1100 + szFolderPath);
1101 + }
1102 + }
1103 +
1104 + internal static uint MsiSummaryInfoGetProperty(int hSummaryInfo, uint uiProperty, out uint uiDataType, out int iValue, ref long ftValue, StringBuilder szValueBuf, ref uint cchValueBuf)
1105 + {
1106 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hSummaryInfo))
1107 + {
1108 + return NativeMethods.MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, out uiDataType, out iValue, ref ftValue, szValueBuf, ref cchValueBuf);
1109 + }
1110 + else lock (RemotableNativeMethods.remotingDelegate)
1111 + {
1112 + ClearData(requestBuf);
1113 + WriteInt(requestBuf, 0, RemotableNativeMethods.GetRemoteHandle(hSummaryInfo));
1114 + WriteInt(requestBuf, 1, (int) uiProperty);
1115 + IntPtr resp;
1116 + remotingDelegate(RemoteMsiFunctionId.MsiSummaryInfoGetProperty, requestBuf, out resp);
1117 + unchecked
1118 + {
1119 + uint ret = (uint) ReadInt(resp, 0);
1120 + if (ret == 0)
1121 + {
1122 + uiDataType = (uint) ReadInt(resp, 1);
1123 + switch ((VarEnum) uiDataType)
1124 + {
1125 + case VarEnum.VT_I2:
1126 + case VarEnum.VT_I4:
1127 + iValue = ReadInt(resp, 2);
1128 + break;
1129 +
1130 + case VarEnum.VT_FILETIME:
1131 + uint ftHigh = (uint) ReadInt(resp, 2);
1132 + uint ftLow = (uint) ReadInt(resp, 3);
1133 + ftValue = ((long) ftHigh) << 32 | ((long) ftLow);
1134 + iValue = 0;
1135 + break;
1136 +
1137 + case VarEnum.VT_LPSTR:
1138 + ReadString(resp, 2, szValueBuf, ref cchValueBuf);
1139 + iValue = 0;
1140 + break;
1141 +
1142 + default:
1143 + iValue = 0;
1144 + break;
1145 + }
1146 + }
1147 + else
1148 + {
1149 + uiDataType = 0;
1150 + iValue = 0;
1151 + }
1152 + return ret;
1153 + }
1154 + }
1155 + }
1156 +
1157 + internal static uint MsiVerifyDiskSpace(int hInstall)
1158 + {
1159 + if (!RemotingEnabled || !RemotableNativeMethods.IsRemoteHandle(hInstall))
1160 + return NativeMethods.MsiVerifyDiskSpace(hInstall);
1161 + else
1162 + {
1163 + return RemotableNativeMethods.MsiFunc_III(
1164 + RemoteMsiFunctionId.MsiVerifyDiskSpace,
1165 + RemotableNativeMethods.GetRemoteHandle(hInstall),
1166 + 0,
1167 + 0);
1168 + }
1169 + }
1170 + }
1171 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Session.cs new
+946
@@ -0,0 +1,946 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Runtime.InteropServices;
10 + using System.Diagnostics.CodeAnalysis;
11 +
12 + /// <summary>
13 + /// The Session object controls the installation process. It opens the
14 + /// install database, which contains the installation tables and data.
15 + /// </summary>
16 + /// <remarks><p>
17 + /// This object is associated with a standard set of action functions,
18 + /// each performing particular operations on data from one or more tables. Additional
19 + /// custom actions may be added for particular product installations. The basic engine
20 + /// function is a sequencer that fetches sequential records from a designated sequence
21 + /// table, evaluates any specified condition expression, and executes the designated
22 + /// action. Actions not recognized by the engine are deferred to the UI handler object
23 + /// for processing, usually dialog box sequences.
24 + /// </p><p>
25 + /// Note that only one Session object can be opened by a single process.
26 + /// </p></remarks>
27 + public sealed class Session : InstallerHandle, IFormatProvider
28 + {
29 + private Database database;
30 + private CustomActionData customActionData;
31 + private bool sessionAccessValidated = false;
32 +
33 + internal Session(IntPtr handle, bool ownsHandle)
34 + : base(handle, ownsHandle)
35 + {
36 + }
37 +
38 + /// <summary>
39 + /// Gets the Database for the install session.
40 + /// </summary>
41 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
42 + /// <exception cref="InstallerException">the Database cannot be accessed</exception>
43 + /// <remarks><p>
44 + /// Normally there is no need to close this Database object. The same object can be
45 + /// used throughout the lifetime of the Session, and it will be closed when the Session
46 + /// is closed.
47 + /// </p><p>
48 + /// Win32 MSI API:
49 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetactivedatabase.asp">MsiGetActiveDatabase</a>
50 + /// </p></remarks>
51 + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
52 + public Database Database
53 + {
54 + get
55 + {
56 + if (this.database == null || this.database.IsClosed)
57 + {
58 + lock (this.Sync)
59 + {
60 + if (this.database == null || this.database.IsClosed)
61 + {
62 + this.ValidateSessionAccess();
63 +
64 + int hDb = RemotableNativeMethods.MsiGetActiveDatabase((int) this.Handle);
65 + if (hDb == 0)
66 + {
67 + throw new InstallerException();
68 + }
69 + this.database = new Database((IntPtr) hDb, true, "", DatabaseOpenMode.ReadOnly);
70 + }
71 + }
72 + }
73 + return this.database;
74 + }
75 + }
76 +
77 + /// <summary>
78 + /// Gets the numeric language ID used by the current install session.
79 + /// </summary>
80 + /// <remarks><p>
81 + /// Win32 MSI API:
82 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlanguage.asp">MsiGetLanguage</a>
83 + /// </p></remarks>
84 + public int Language
85 + {
86 + get
87 + {
88 + return (int) RemotableNativeMethods.MsiGetLanguage((int) this.Handle);
89 + }
90 + }
91 +
92 + /// <summary>
93 + /// Gets or sets the string value of a named installer property, as maintained by the
94 + /// Session object in the in-memory Property table, or, if it is prefixed with a percent
95 + /// sign (%), the value of a system environment variable for the current process.
96 + /// </summary>
97 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
98 + /// <remarks><p>
99 + /// Win32 MSI APIs:
100 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproperty.asp">MsiGetProperty</a>,
101 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetproperty.asp">MsiSetProperty</a>
102 + /// </p></remarks>
103 + public string this[string property]
104 + {
105 + get
106 + {
107 + if (String.IsNullOrEmpty(property))
108 + {
109 + throw new ArgumentNullException("property");
110 + }
111 +
112 + if (!this.sessionAccessValidated &&
113 + !Session.NonImmediatePropertyNames.Contains(property))
114 + {
115 + this.ValidateSessionAccess();
116 + }
117 +
118 + StringBuilder buf = new StringBuilder();
119 + uint bufSize = 0;
120 + uint ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize);
121 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
122 + {
123 + buf.Capacity = (int) ++bufSize;
124 + ret = RemotableNativeMethods.MsiGetProperty((int) this.Handle, property, buf, ref bufSize);
125 + }
126 +
127 + if (ret != 0)
128 + {
129 + throw InstallerException.ExceptionFromReturnCode(ret);
130 + }
131 + return buf.ToString();
132 + }
133 +
134 + set
135 + {
136 + if (String.IsNullOrEmpty(property))
137 + {
138 + throw new ArgumentNullException("property");
139 + }
140 +
141 + this.ValidateSessionAccess();
142 +
143 + if (value == null)
144 + {
145 + value = String.Empty;
146 + }
147 +
148 + uint ret = RemotableNativeMethods.MsiSetProperty((int) this.Handle, property, value);
149 + if (ret != 0)
150 + {
151 + throw InstallerException.ExceptionFromReturnCode(ret);
152 + }
153 + }
154 + }
155 +
156 + /// <summary>
157 + /// Creates a new Session object from an integer session handle.
158 + /// </summary>
159 + /// <param name="handle">Integer session handle</param>
160 + /// <param name="ownsHandle">true to close the handle when this object is disposed or finalized</param>
161 + /// <remarks><p>
162 + /// This method is only provided for interop purposes. A Session object
163 + /// should normally be obtained by calling <see cref="Installer.OpenPackage(Database,bool)"/>
164 + /// or <see cref="Installer.OpenProduct"/>.
165 + /// </p></remarks>
166 + public static Session FromHandle(IntPtr handle, bool ownsHandle)
167 + {
168 + return new Session(handle, ownsHandle);
169 + }
170 +
171 + /// <summary>
172 + /// Performs any enabled logging operations and defers execution to the UI handler
173 + /// object associated with the engine.
174 + /// </summary>
175 + /// <param name="messageType">Type of message to be processed</param>
176 + /// <param name="record">Contains message-specific fields</param>
177 + /// <returns>A message-dependent return value</returns>
178 + /// <exception cref="InvalidHandleException">the Session or Record handle is invalid</exception>
179 + /// <exception cref="ArgumentOutOfRangeException">an invalid message kind is specified</exception>
180 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
181 + /// <exception cref="InstallerException">the message-handler failed for an unknown reason</exception>
182 + /// <remarks><p>
183 + /// Logging may be selectively enabled for the various message types.
184 + /// See the <see cref="Installer.EnableLog(InstallLogModes,string)"/> method.
185 + /// </p><p>
186 + /// If record field 0 contains a formatting string, it is used to format the data in
187 + /// the other fields. Else if the message is an error, warning, or user message, an attempt
188 + /// is made to find a message template in the Error table for the current database using the
189 + /// error number found in field 1 of the record for message types and return values.
190 + /// </p><p>
191 + /// The <paramref name="messageType"/> parameter may also include message-box flags from
192 + /// the following enumerations: System.Windows.Forms.MessageBoxButtons,
193 + /// System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxIcon. These
194 + /// flags can be combined with the InstallMessage with a bitwise OR.
195 + /// </p><p>
196 + /// Note, this method never returns Cancel or Error values. Instead, appropriate
197 + /// exceptions are thrown in those cases.
198 + /// </p><p>
199 + /// Win32 MSI API:
200 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
201 + /// </p></remarks>
202 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
203 + public MessageResult Message(InstallMessage messageType, Record record)
204 + {
205 + if (record == null)
206 + {
207 + throw new ArgumentNullException("record");
208 + }
209 +
210 + int ret = RemotableNativeMethods.MsiProcessMessage((int) this.Handle, (uint) messageType, (int) record.Handle);
211 + if (ret < 0)
212 + {
213 + throw new InstallerException();
214 + }
215 + else if (ret == (int) MessageResult.Cancel)
216 + {
217 + throw new InstallCanceledException();
218 + }
219 + return (MessageResult) ret;
220 + }
221 +
222 + /// <summary>
223 + /// Writes a message to the log, if logging is enabled.
224 + /// </summary>
225 + /// <param name="msg">The line to be written to the log</param>
226 + /// <remarks><p>
227 + /// Win32 MSI API:
228 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
229 + /// </p></remarks>
230 + public void Log(string msg)
231 + {
232 + if (msg == null)
233 + {
234 + throw new ArgumentNullException("msg");
235 + }
236 +
237 + using (Record rec = new Record(0))
238 + {
239 + rec.FormatString = msg;
240 + this.Message(InstallMessage.Info, rec);
241 + }
242 + }
243 +
244 + /// <summary>
245 + /// Writes a formatted message to the log, if logging is enabled.
246 + /// </summary>
247 + /// <param name="format">The line to be written to the log, containing 0 or more format specifiers</param>
248 + /// <param name="args">An array containing 0 or more objects to be formatted</param>
249 + /// <remarks><p>
250 + /// Win32 MSI API:
251 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiprocessmessage.asp">MsiProcessMessage</a>
252 + /// </p></remarks>
253 + public void Log(string format, params object[] args)
254 + {
255 + this.Log(String.Format(CultureInfo.InvariantCulture, format, args));
256 + }
257 +
258 + /// <summary>
259 + /// Evaluates a logical expression containing symbols and values.
260 + /// </summary>
261 + /// <param name="condition">conditional expression</param>
262 + /// <returns>The result of the condition evaluation</returns>
263 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
264 + /// <exception cref="ArgumentNullException">the condition is null or empty</exception>
265 + /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception>
266 + /// <remarks><p>
267 + /// Win32 MSI API:
268 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a>
269 + /// </p></remarks>
270 + public bool EvaluateCondition(string condition)
271 + {
272 + if (String.IsNullOrEmpty(condition))
273 + {
274 + throw new ArgumentNullException("condition");
275 + }
276 +
277 + uint value = RemotableNativeMethods.MsiEvaluateCondition((int) this.Handle, condition);
278 + if (value == 0)
279 + {
280 + return false;
281 + }
282 + else if (value == 1)
283 + {
284 + return true;
285 + }
286 + else
287 + {
288 + throw new InvalidOperationException();
289 + }
290 + }
291 +
292 + /// <summary>
293 + /// Evaluates a logical expression containing symbols and values, specifying a default
294 + /// value to be returned in case the condition is empty.
295 + /// </summary>
296 + /// <param name="condition">conditional expression</param>
297 + /// <param name="defaultValue">value to return if the condition is empty</param>
298 + /// <returns>The result of the condition evaluation</returns>
299 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
300 + /// <exception cref="InvalidOperationException">the conditional expression is invalid</exception>
301 + /// <remarks><p>
302 + /// Win32 MSI API:
303 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msievaluatecondition.asp">MsiEvaluateCondition</a>
304 + /// </p></remarks>
305 + public bool EvaluateCondition(string condition, bool defaultValue)
306 + {
307 + if (condition == null)
308 + {
309 + throw new ArgumentNullException("condition");
310 + }
311 + else if (condition.Length == 0)
312 + {
313 + return defaultValue;
314 + }
315 + else
316 + {
317 + this.ValidateSessionAccess();
318 + return this.EvaluateCondition(condition);
319 + }
320 + }
321 +
322 + /// <summary>
323 + /// Formats a string containing installer properties.
324 + /// </summary>
325 + /// <param name="format">A format string containing property tokens</param>
326 + /// <returns>A formatted string containing property data</returns>
327 + /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
328 + /// <remarks><p>
329 + /// Win32 MSI API:
330 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
331 + /// </p></remarks>
332 + [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames")]
333 + public string Format(string format)
334 + {
335 + if (format == null)
336 + {
337 + throw new ArgumentNullException("format");
338 + }
339 +
340 + using (Record formatRec = new Record(0))
341 + {
342 + formatRec.FormatString = format;
343 + return formatRec.ToString(this);
344 + }
345 + }
346 +
347 + /// <summary>
348 + /// Returns a formatted string from record data.
349 + /// </summary>
350 + /// <param name="record">Record object containing a template and data to be formatted.
351 + /// The template string must be set in field 0 followed by any referenced data parameters.</param>
352 + /// <returns>A formatted string containing the record data</returns>
353 + /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
354 + /// <remarks><p>
355 + /// Win32 MSI API:
356 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
357 + /// </p></remarks>
358 + public string FormatRecord(Record record)
359 + {
360 + if (record == null)
361 + {
362 + throw new ArgumentNullException("record");
363 + }
364 +
365 + return record.ToString(this);
366 + }
367 +
368 + /// <summary>
369 + /// Returns a formatted string from record data using a specified format.
370 + /// </summary>
371 + /// <param name="record">Record object containing a template and data to be formatted</param>
372 + /// <param name="format">Format string to be used instead of field 0 of the Record</param>
373 + /// <returns>A formatted string containing the record data</returns>
374 + /// <exception cref="InvalidHandleException">the Record handle is invalid</exception>
375 + /// <remarks><p>
376 + /// Win32 MSI API:
377 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiformatrecord.asp">MsiFormatRecord</a>
378 + /// </p></remarks>
379 + [Obsolete("This method is obsolete because it has undesirable side-effects. As an alternative, set the Record's " +
380 + "FormatString property separately before calling the FormatRecord() override that takes only the Record parameter.")]
381 + public string FormatRecord(Record record, string format)
382 + {
383 + if (record == null)
384 + {
385 + throw new ArgumentNullException("record");
386 + }
387 +
388 + return record.ToString(format, this);
389 + }
390 +
391 + /// <summary>
392 + /// Retrieves product properties (not session properties) from the product database.
393 + /// </summary>
394 + /// <returns>Value of the property, or an empty string if the property is not set.</returns>
395 + /// <remarks><p>
396 + /// Note this is not the correct method for getting ordinary session properties. For that,
397 + /// see the indexer on the Session class.
398 + /// </p><p>
399 + /// Win32 MSI API:
400 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductproperty.asp">MsiGetProductProperty</a>
401 + /// </p></remarks>
402 + public string GetProductProperty(string property)
403 + {
404 + if (String.IsNullOrEmpty(property))
405 + {
406 + throw new ArgumentNullException("property");
407 + }
408 +
409 + this.ValidateSessionAccess();
410 +
411 + StringBuilder buf = new StringBuilder();
412 + uint bufSize = (uint) buf.Capacity;
413 + uint ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize);
414 +
415 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
416 + {
417 + buf.Capacity = (int) ++bufSize;
418 + ret = NativeMethods.MsiGetProductProperty((int) this.Handle, property, buf, ref bufSize);
419 + }
420 +
421 + if (ret != 0)
422 + {
423 + throw InstallerException.ExceptionFromReturnCode(ret);
424 + }
425 + return buf.ToString();
426 + }
427 +
428 + /// <summary>
429 + /// Gets an accessor for components in the current session.
430 + /// </summary>
431 + public ComponentInfoCollection Components
432 + {
433 + get
434 + {
435 + this.ValidateSessionAccess();
436 + return new ComponentInfoCollection(this);
437 + }
438 + }
439 +
440 + /// <summary>
441 + /// Gets an accessor for features in the current session.
442 + /// </summary>
443 + public FeatureInfoCollection Features
444 + {
445 + get
446 + {
447 + this.ValidateSessionAccess();
448 + return new FeatureInfoCollection(this);
449 + }
450 + }
451 +
452 + /// <summary>
453 + /// Checks to see if sufficient disk space is present for the current installation.
454 + /// </summary>
455 + /// <returns>True if there is sufficient disk space; false otherwise.</returns>
456 + /// <remarks><p>
457 + /// Win32 MSI API:
458 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiverifydiskspace.asp">MsiVerifyDiskSpace</a>
459 + /// </p></remarks>
460 + public bool VerifyDiskSpace()
461 + {
462 + this.ValidateSessionAccess();
463 +
464 + uint ret = RemotableNativeMethods.MsiVerifyDiskSpace((int)this.Handle);
465 + if (ret == (uint) NativeMethods.Error.DISK_FULL)
466 + {
467 + return false;
468 + }
469 + else if (ret != 0)
470 + {
471 + throw InstallerException.ExceptionFromReturnCode(ret);
472 + }
473 + return true;
474 + }
475 +
476 + /// <summary>
477 + /// Gets the total disk space per drive required for the installation.
478 + /// </summary>
479 + /// <returns>A list of InstallCost structures, specifying the cost for each drive</returns>
480 + /// <remarks><p>
481 + /// Win32 MSI API:
482 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a>
483 + /// </p></remarks>
484 + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
485 + public IList<InstallCost> GetTotalCost()
486 + {
487 + this.ValidateSessionAccess();
488 +
489 + IList<InstallCost> costs = new List<InstallCost>();
490 + StringBuilder driveBuf = new StringBuilder(20);
491 + for (uint i = 0; true; i++)
492 + {
493 + int cost, tempCost;
494 + uint driveBufSize = (uint) driveBuf.Capacity;
495 + uint ret = RemotableNativeMethods.MsiEnumComponentCosts(
496 + (int) this.Handle,
497 + null,
498 + i,
499 + (int) InstallState.Default,
500 + driveBuf,
501 + ref driveBufSize,
502 + out cost,
503 + out tempCost);
504 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
505 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
506 + {
507 + driveBuf.Capacity = (int) ++driveBufSize;
508 + ret = RemotableNativeMethods.MsiEnumComponentCosts(
509 + (int) this.Handle,
510 + null,
511 + i,
512 + (int) InstallState.Default,
513 + driveBuf,
514 + ref driveBufSize,
515 + out cost,
516 + out tempCost);
517 + }
518 +
519 + if (ret != 0)
520 + {
521 + throw InstallerException.ExceptionFromReturnCode(ret);
522 + }
523 + costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L));
524 + }
525 + return costs;
526 + }
527 +
528 + /// <summary>
529 + /// Gets the designated mode flag for the current install session.
530 + /// </summary>
531 + /// <param name="mode">The type of mode to be checked.</param>
532 + /// <returns>The value of the designated mode flag.</returns>
533 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
534 + /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception>
535 + /// <remarks><p>
536 + /// Note that only the following run modes are available to read from
537 + /// a deferred custom action:<list type="bullet">
538 + /// <item><description><see cref="InstallRunMode.Scheduled"/></description></item>
539 + /// <item><description><see cref="InstallRunMode.Rollback"/></description></item>
540 + /// <item><description><see cref="InstallRunMode.Commit"/></description></item>
541 + /// </list>
542 + /// </p><p>
543 + /// Win32 MSI API:
544 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetmode.asp">MsiGetMode</a>
545 + /// </p></remarks>
546 + public bool GetMode(InstallRunMode mode)
547 + {
548 + return RemotableNativeMethods.MsiGetMode((int) this.Handle, (uint) mode);
549 + }
550 +
551 + /// <summary>
552 + /// Sets the designated mode flag for the current install session.
553 + /// </summary>
554 + /// <param name="mode">The type of mode to be set.</param>
555 + /// <param name="value">The desired value of the mode.</param>
556 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
557 + /// <exception cref="ArgumentOutOfRangeException">an invalid mode flag was specified</exception>
558 + /// <exception cref="InvalidOperationException">the mode cannot not be set</exception>
559 + /// <remarks><p>
560 + /// Win32 MSI API:
561 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetmode.asp">MsiSetMode</a>
562 + /// </p></remarks>
563 + public void SetMode(InstallRunMode mode, bool value)
564 + {
565 + this.ValidateSessionAccess();
566 +
567 + uint ret = RemotableNativeMethods.MsiSetMode((int) this.Handle, (uint) mode, value);
568 + if (ret != 0)
569 + {
570 + if (ret == (uint) NativeMethods.Error.ACCESS_DENIED)
571 + {
572 + throw new InvalidOperationException();
573 + }
574 + else
575 + {
576 + throw InstallerException.ExceptionFromReturnCode(ret);
577 + }
578 + }
579 + }
580 +
581 + /// <summary>
582 + /// Gets the full path to the designated folder on the source media or server image.
583 + /// </summary>
584 + /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
585 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
586 + /// <remarks><p>
587 + /// Win32 MSI API:
588 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsourcepath.asp">MsiGetSourcePath</a>
589 + /// </p></remarks>
590 + public string GetSourcePath(string directory)
591 + {
592 + if (String.IsNullOrEmpty(directory))
593 + {
594 + throw new ArgumentNullException("directory");
595 + }
596 +
597 + this.ValidateSessionAccess();
598 +
599 + StringBuilder buf = new StringBuilder();
600 + uint bufSize = 0;
601 + uint ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize);
602 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
603 + {
604 + buf.Capacity = (int) ++bufSize;
605 + ret = ret = RemotableNativeMethods.MsiGetSourcePath((int) this.Handle, directory, buf, ref bufSize);
606 + }
607 +
608 + if (ret != 0)
609 + {
610 + if (ret == (uint) NativeMethods.Error.DIRECTORY)
611 + {
612 + throw InstallerException.ExceptionFromReturnCode(ret, directory);
613 + }
614 + else
615 + {
616 + throw InstallerException.ExceptionFromReturnCode(ret);
617 + }
618 + }
619 + return buf.ToString();
620 + }
621 +
622 + /// <summary>
623 + /// Gets the full path to the designated folder on the installation target drive.
624 + /// </summary>
625 + /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
626 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
627 + /// <remarks><p>
628 + /// Win32 MSI API:
629 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigettargetpath.asp">MsiGetTargetPath</a>
630 + /// </p></remarks>
631 + public string GetTargetPath(string directory)
632 + {
633 + if (String.IsNullOrEmpty(directory))
634 + {
635 + throw new ArgumentNullException("directory");
636 + }
637 +
638 + this.ValidateSessionAccess();
639 +
640 + StringBuilder buf = new StringBuilder();
641 + uint bufSize = 0;
642 + uint ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize);
643 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
644 + {
645 + buf.Capacity = (int) ++bufSize;
646 + ret = ret = RemotableNativeMethods.MsiGetTargetPath((int) this.Handle, directory, buf, ref bufSize);
647 + }
648 +
649 + if (ret != 0)
650 + {
651 + if (ret == (uint) NativeMethods.Error.DIRECTORY)
652 + {
653 + throw InstallerException.ExceptionFromReturnCode(ret, directory);
654 + }
655 + else
656 + {
657 + throw InstallerException.ExceptionFromReturnCode(ret);
658 + }
659 + }
660 + return buf.ToString();
661 + }
662 +
663 + /// <summary>
664 + /// Sets the full path to the designated folder on the installation target drive.
665 + /// </summary>
666 + /// <exception cref="ArgumentException">the folder was not found in the Directory table</exception>
667 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
668 + /// <remarks><p>
669 + /// Setting the target path of a directory changes the path specification for the directory
670 + /// in the in-memory Directory table. Also, the path specifications of all other path objects
671 + /// in the table that are either subordinate or equivalent to the changed path are updated
672 + /// to reflect the change. The properties for each affected path are also updated.
673 + /// </p><p>
674 + /// If an error occurs in this function, all updated paths and properties revert to
675 + /// their previous values. Therefore, it is safe to treat errors returned by this function
676 + /// as non-fatal.
677 + /// </p><p>
678 + /// Do not attempt to configure the target path if the components using those paths
679 + /// are already installed for the current user or for a different user. Check the
680 + /// ProductState property before setting the target path to determine if the product
681 + /// containing this component is installed.
682 + /// </p><p>
683 + /// Win32 MSI API:
684 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisettargetpath.asp">MsiSetTargetPath</a>
685 + /// </p></remarks>
686 + public void SetTargetPath(string directory, string value)
687 + {
688 + if (String.IsNullOrEmpty(directory))
689 + {
690 + throw new ArgumentNullException("directory");
691 + }
692 +
693 + if (value == null)
694 + {
695 + throw new ArgumentNullException("value");
696 + }
697 +
698 + this.ValidateSessionAccess();
699 +
700 + uint ret = RemotableNativeMethods.MsiSetTargetPath((int) this.Handle, directory, value);
701 + if (ret != 0)
702 + {
703 + if (ret == (uint) NativeMethods.Error.DIRECTORY)
704 + {
705 + throw InstallerException.ExceptionFromReturnCode(ret, directory);
706 + }
707 + else
708 + {
709 + throw InstallerException.ExceptionFromReturnCode(ret);
710 + }
711 + }
712 + }
713 +
714 + /// <summary>
715 + /// Sets the install level for the current installation to a specified value and
716 + /// recalculates the Select and Installed states for all features in the Feature
717 + /// table. Also sets the Action state of each component in the Component table based
718 + /// on the new level.
719 + /// </summary>
720 + /// <param name="installLevel">New install level</param>
721 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
722 + /// <remarks><p>
723 + /// The SetInstallLevel method sets the following:<list type="bullet">
724 + /// <item><description>The installation level for the current installation to a specified value</description></item>
725 + /// <item><description>The Select and Installed states for all features in the Feature table</description></item>
726 + /// <item><description>The Action state of each component in the Component table, based on the new level</description></item>
727 + /// </list>
728 + /// If 0 or a negative number is passed in the ilnstallLevel parameter,
729 + /// the current installation level does not change, but all features are still
730 + /// updated based on the current installation level.
731 + /// </p><p>
732 + /// Win32 MSI API:
733 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetinstalllevel.asp">MsiSetInstallLevel</a>
734 + /// </p></remarks>
735 + public void SetInstallLevel(int installLevel)
736 + {
737 + this.ValidateSessionAccess();
738 +
739 + uint ret = RemotableNativeMethods.MsiSetInstallLevel((int) this.Handle, installLevel);
740 + if (ret != 0)
741 + {
742 + throw InstallerException.ExceptionFromReturnCode(ret);
743 + }
744 + }
745 +
746 + /// <summary>
747 + /// Executes a built-in action, custom action, or user-interface wizard action.
748 + /// </summary>
749 + /// <param name="action">Name of the action to execute. Case-sensitive.</param>
750 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
751 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
752 + /// <remarks><p>
753 + /// The DoAction method executes the action that corresponds to the name supplied. If the
754 + /// name is not recognized by the installer as a built-in action or as a custom action in
755 + /// the CustomAction table, the name is passed to the user-interface handler object, which
756 + /// can invoke a function or a dialog box. If a null action name is supplied, the installer
757 + /// uses the upper-case value of the ACTION property as the action to perform. If no property
758 + /// value is defined, the default action is performed, defined as "INSTALL".
759 + /// </p><p>
760 + /// Actions that update the system, such as the InstallFiles and WriteRegistryValues
761 + /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction
762 + /// is called from a custom action that is scheduled in the InstallExecuteSequence table
763 + /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the
764 + /// system, such as AppSearch or CostInitialize, can be called.
765 + /// </p><p>
766 + /// Win32 MSI API:
767 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a>
768 + /// </p></remarks>
769 + public void DoAction(string action)
770 + {
771 + this.DoAction(action, null);
772 + }
773 +
774 + /// <summary>
775 + /// Executes a built-in action, custom action, or user-interface wizard action.
776 + /// </summary>
777 + /// <param name="action">Name of the action to execute. Case-sensitive.</param>
778 + /// <param name="actionData">Optional data to be passed to a deferred custom action.</param>
779 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
780 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
781 + /// <remarks><p>
782 + /// The DoAction method executes the action that corresponds to the name supplied. If the
783 + /// name is not recognized by the installer as a built-in action or as a custom action in
784 + /// the CustomAction table, the name is passed to the user-interface handler object, which
785 + /// can invoke a function or a dialog box. If a null action name is supplied, the installer
786 + /// uses the upper-case value of the ACTION property as the action to perform. If no property
787 + /// value is defined, the default action is performed, defined as "INSTALL".
788 + /// </p><p>
789 + /// Actions that update the system, such as the InstallFiles and WriteRegistryValues
790 + /// actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction
791 + /// is called from a custom action that is scheduled in the InstallExecuteSequence table
792 + /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the
793 + /// system, such as AppSearch or CostInitialize, can be called.
794 + /// </p><p>
795 + /// If the called action is a deferred, rollback, or commit custom action, then the supplied
796 + /// <paramref name="actionData"/> will be available via the <see cref="CustomActionData"/>
797 + /// property of that custom action's session.
798 + /// </p><p>
799 + /// Win32 MSI API:
800 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msidoaction.asp">MsiDoAction</a>
801 + /// </p></remarks>
802 + public void DoAction(string action, CustomActionData actionData)
803 + {
804 + if (String.IsNullOrEmpty(action))
805 + {
806 + throw new ArgumentNullException("action");
807 + }
808 +
809 + this.ValidateSessionAccess();
810 +
811 + if (actionData != null)
812 + {
813 + this[action] = actionData.ToString();
814 + }
815 +
816 + uint ret = RemotableNativeMethods.MsiDoAction((int) this.Handle, action);
817 + if (ret != 0)
818 + {
819 + throw InstallerException.ExceptionFromReturnCode(ret);
820 + }
821 + }
822 +
823 + /// <summary>
824 + /// Executes an action sequence described in the specified table.
825 + /// </summary>
826 + /// <param name="sequenceTable">Name of the table containing the action sequence.</param>
827 + /// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
828 + /// <exception cref="InstallCanceledException">the user exited the installation</exception>
829 + /// <remarks><p>
830 + /// This method queries the specified table, ordering the actions by the numbers in the Sequence column.
831 + /// For each row retrieved, an action is executed, provided that any supplied condition expression does
832 + /// not evaluate to FALSE.
833 + /// </p><p>
834 + /// An action sequence containing any actions that update the system, such as the InstallFiles and
835 + /// WriteRegistryValues actions, cannot be run by calling DoActionSequence. The exception to this rule is if
836 + /// DoActionSequence is called from a custom action that is scheduled in the InstallExecuteSequence table
837 + /// between the InstallInitialize and InstallFinalize actions. Actions that do not update the system, such
838 + /// as AppSearch or CostInitialize, can be called.
839 + /// </p><p>
840 + /// Win32 MSI API:
841 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisequence.asp">MsiSequence</a>
842 + /// </p></remarks>
843 + public void DoActionSequence(string sequenceTable)
844 + {
845 + if (String.IsNullOrEmpty(sequenceTable))
846 + {
847 + throw new ArgumentNullException("sequenceTable");
848 + }
849 +
850 + this.ValidateSessionAccess();
851 +
852 + uint ret = RemotableNativeMethods.MsiSequence((int) this.Handle, sequenceTable, 0);
853 + if (ret != 0)
854 + {
855 + throw InstallerException.ExceptionFromReturnCode(ret);
856 + }
857 + }
858 +
859 + /// <summary>
860 + /// Gets custom action data for the session that was supplied by the caller.
861 + /// </summary>
862 + /// <seealso cref="DoAction(string,CustomActionData)"/>
863 + public CustomActionData CustomActionData
864 + {
865 + get
866 + {
867 + if (this.customActionData == null)
868 + {
869 + this.customActionData = new CustomActionData(this[CustomActionData.PropertyName]);
870 + }
871 +
872 + return this.customActionData;
873 + }
874 + }
875 +
876 + /// <summary>
877 + /// Implements formatting for <see cref="Record" /> data.
878 + /// </summary>
879 + /// <param name="formatType">Type of format object to get.</param>
880 + /// <returns>The the current instance, if <paramref name="formatType"/> is the same type
881 + /// as the current instance; otherwise, null.</returns>
882 + object IFormatProvider.GetFormat(Type formatType)
883 + {
884 + return formatType == typeof(Session) ? this : null;
885 + }
886 +
887 + /// <summary>
888 + /// Closes the session handle. Also closes the active database handle, if it is open.
889 + /// After closing a handle, further method calls may throw an <see cref="InvalidHandleException"/>.
890 + /// </summary>
891 + /// <param name="disposing">If true, the method has been called directly
892 + /// or indirectly by a user's code, so managed and unmanaged resources will
893 + /// be disposed. If false, only unmanaged resources will be disposed.</param>
894 + protected override void Dispose(bool disposing)
895 + {
896 + try
897 + {
898 + if (disposing)
899 + {
900 + if (this.database != null)
901 + {
902 + this.database.Dispose();
903 + this.database = null;
904 + }
905 + }
906 + }
907 + finally
908 + {
909 + base.Dispose(disposing);
910 + }
911 + }
912 +
913 + /// <summary>
914 + /// Gets the (short) list of properties that are available from non-immediate custom actions.
915 + /// </summary>
916 + private static IList<string> NonImmediatePropertyNames
917 + {
918 + get
919 + {
920 + return new string[] {
921 + CustomActionData.PropertyName,
922 + "ProductCode",
923 + "UserSID"
924 + };
925 + }
926 + }
927 +
928 + /// <summary>
929 + /// Throws an exception if the custom action is not able to access immedate session details.
930 + /// </summary>
931 + private void ValidateSessionAccess()
932 + {
933 + if (!this.sessionAccessValidated)
934 + {
935 + if (this.GetMode(InstallRunMode.Scheduled) ||
936 + this.GetMode(InstallRunMode.Rollback) ||
937 + this.GetMode(InstallRunMode.Commit))
938 + {
939 + throw new InstallerException("Cannot access session details from a non-immediate custom action");
940 + }
941 +
942 + this.sessionAccessValidated = true;
943 + }
944 + }
945 + }
946 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ShortcutTarget.cs new
+104
@@ -0,0 +1,104 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + /// <summary>
6 + /// Holds information about the target of a shortcut file.
7 + /// </summary>
8 + public struct ShortcutTarget
9 + {
10 + private string productCode;
11 + private string feature;
12 + private string componentCode;
13 +
14 + internal ShortcutTarget(string productCode, string feature, string componentCode)
15 + {
16 + this.productCode = productCode;
17 + this.feature = feature;
18 + this.componentCode = componentCode;
19 + }
20 +
21 + /// <summary>
22 + /// Gets the target product code of the shortcut, or null if not available.
23 + /// </summary>
24 + public string ProductCode
25 + {
26 + get
27 + {
28 + return this.productCode;
29 + }
30 + }
31 +
32 + /// <summary>
33 + /// Gets the name of the target feature of the shortcut, or null if not available.
34 + /// </summary>
35 + public string Feature
36 + {
37 + get
38 + {
39 + return this.feature;
40 + }
41 + }
42 +
43 + /// <summary>
44 + /// Gets the target component code of the shortcut, or null if not available.
45 + /// </summary>
46 + public string ComponentCode
47 + {
48 + get
49 + {
50 + return this.componentCode;
51 + }
52 + }
53 +
54 + /// <summary>
55 + /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
56 + /// </summary>
57 + /// <param name="st1">The first shortcut target to compare.</param>
58 + /// <param name="st2">The second shortcut target to compare.</param>
59 + /// <returns>True if all parts of the targets are the same, else false.</returns>
60 + public static bool operator ==(ShortcutTarget st1, ShortcutTarget st2)
61 + {
62 + return st1.Equals(st2);
63 + }
64 +
65 + /// <summary>
66 + /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
67 + /// </summary>
68 + /// <param name="st1">The first shortcut target to compare.</param>
69 + /// <param name="st2">The second shortcut target to compare.</param>
70 + /// <returns>True if any parts of the targets are different, else false.</returns>
71 + public static bool operator !=(ShortcutTarget st1, ShortcutTarget st2)
72 + {
73 + return !st1.Equals(st2);
74 + }
75 +
76 + /// <summary>
77 + /// Tests whether two shortcut targets have the same product code, feature, and/or component code.
78 + /// </summary>
79 + /// <param name="obj">The shortcut target to compare to the current object.</param>
80 + /// <returns>True if <paramref name="obj"/> is a shortcut target and all parts of the targets are the same, else false.</returns>
81 + public override bool Equals(object obj)
82 + {
83 + if (obj == null || obj.GetType() != typeof(ShortcutTarget))
84 + {
85 + return false;
86 + }
87 + ShortcutTarget st = (ShortcutTarget) obj;
88 + return this.productCode == st.productCode
89 + && this.feature == st.feature
90 + && this.componentCode == st.componentCode;
91 + }
92 +
93 + /// <summary>
94 + /// Generates a hash code using all parts of the shortcut target.
95 + /// </summary>
96 + /// <returns>An integer suitable for hashing the shortcut target.</returns>
97 + public override int GetHashCode()
98 + {
99 + return (this.productCode != null ? this.productCode.GetHashCode() : 0)
100 + ^ (this.feature != null ? this.feature.GetHashCode() : 0)
101 + ^ (this.componentCode != null ? this.componentCode.GetHashCode() : 0);
102 + }
103 + }
104 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/SourceList.cs new
+525
@@ -0,0 +1,525 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// A list of sources for an installed product or patch.
13 + /// </summary>
14 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
15 + public class SourceList : ICollection<string>
16 + {
17 + private Installation installation;
18 + private SourceMediaList mediaList;
19 +
20 + internal SourceList(Installation installation)
21 + {
22 + this.installation = installation;
23 + }
24 +
25 + /// <summary>
26 + /// Gets the list of disks registered for the media source of
27 + /// the patch or product installation.
28 + /// </summary>
29 + public SourceMediaList MediaList
30 + {
31 + get
32 + {
33 + if (this.mediaList == null)
34 + {
35 + this.mediaList = new SourceMediaList(this.installation);
36 + }
37 + return this.mediaList;
38 + }
39 + }
40 +
41 + /// <summary>
42 + /// Gets the number of network and URL sources in the list.
43 + /// </summary>
44 + public int Count
45 + {
46 + get
47 + {
48 + int count = 0;
49 + IEnumerator<string> e = this.GetEnumerator();
50 + while (e.MoveNext())
51 + {
52 + count++;
53 + }
54 + return count;
55 + }
56 + }
57 +
58 + /// <summary>
59 + /// Gets a boolean value indicating whether the list is read-only.
60 + /// A SourceList is never read-only.
61 + /// </summary>
62 + /// <value>read-only status of the list</value>
63 + public bool IsReadOnly
64 + {
65 + get { return false; }
66 + }
67 +
68 + /// <summary>
69 + /// Adds a network or URL source to the source list of the installed product.
70 + /// </summary>
71 + /// <param name="item">Path to the source to be added. This parameter is
72 + /// expected to contain only the path without the filename.</param>
73 + /// <remarks><p>
74 + /// If this method is called with a new source, the installer adds the source
75 + /// to the end of the source list.
76 + /// </p><p>
77 + /// If this method is called with a source already existing in the source
78 + /// list, it has no effect.
79 + /// </p><p>
80 + /// Win32 MSI APIs:
81 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsource.asp">MsiSourceListAddSource</a>,
82 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a>
83 + /// </p></remarks>
84 + /// <seealso cref="Insert"/>
85 + public void Add(string item)
86 + {
87 + if (!this.Contains(item))
88 + {
89 + this.Insert(item, 0);
90 + }
91 + }
92 +
93 + /// <summary>
94 + /// Adds or reorders a network or URL source for the product or patch.
95 + /// </summary>
96 + /// <param name="item">Path to the source to be added. This parameter is
97 + /// expected to contain only the path without the filename.</param>
98 + /// <param name="index">Specifies the priority order in which the source
99 + /// will be inserted</param>
100 + /// <remarks><p>
101 + /// If this method is called with a new source and <paramref name="index"/>
102 + /// is set to 0, the installer adds the source to the end of the source list.
103 + /// </p><p>
104 + /// If this method is called with a source already existing in the source
105 + /// list and <paramref name="index"/> is set to 0, the installer retains the
106 + /// source's existing index.
107 + /// </p><p>
108 + /// If the method is called with an existing source in the source list
109 + /// and <paramref name="index"/> is set to a non-zero value, the source is
110 + /// removed from its current location in the list and inserted at the position
111 + /// specified by Index, before any source that already exists at that position.
112 + /// </p><p>
113 + /// If the method is called with a new source and Index is set to a
114 + /// non-zero value, the source is inserted at the position specified by
115 + /// <paramref name="index"/>, before any source that already exists at
116 + /// that position. The index value for all sources in the list after the
117 + /// index specified by Index are updated to ensure unique index values and
118 + /// the pre-existing order is guaranteed to remain unchanged.
119 + /// </p><p>
120 + /// If <paramref name="index"/> is greater than the number of sources
121 + /// in the list, the source is placed at the end of the list with an index
122 + /// value one larger than any existing source.
123 + /// </p><p>
124 + /// Win32 MSI API:
125 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddsourceex.asp">MsiSourceListAddSourceEx</a>
126 + /// </p></remarks>
127 + public void Insert(string item, int index)
128 + {
129 + if (item == null)
130 + {
131 + throw new ArgumentNullException("item");
132 + }
133 +
134 + NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network;
135 +
136 + uint ret = NativeMethods.MsiSourceListAddSourceEx(
137 + this.installation.InstallationCode,
138 + this.installation.UserSid,
139 + this.installation.Context,
140 + (uint) type | (uint) this.installation.InstallationType,
141 + item,
142 + (uint) index);
143 + if (ret != 0)
144 + {
145 + throw InstallerException.ExceptionFromReturnCode(ret);
146 + }
147 + }
148 +
149 + /// <summary>
150 + /// Clears sources of all types: network, url, and media.
151 + /// </summary>
152 + /// <remarks><p>
153 + /// Win32 MSI API:
154 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearall.asp">MsiSourceListClearAll</a>
155 + /// </p></remarks>
156 + public void Clear()
157 + {
158 + this.ClearSourceType(NativeMethods.SourceType.Url);
159 + this.ClearSourceType(NativeMethods.SourceType.Network);
160 + this.MediaList.Clear();
161 + }
162 +
163 + /// <summary>
164 + /// Removes all network sources from the list. URL sources are not affected.
165 + /// </summary>
166 + /// <remarks><p>
167 + /// Win32 MSI API:
168 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
169 + /// </p></remarks>
170 + public void ClearNetworkSources()
171 + {
172 + this.ClearSourceType(NativeMethods.SourceType.Network);
173 + }
174 +
175 + /// <summary>
176 + /// Removes all URL sources from the list. Network sources are not affected.
177 + /// </summary>
178 + /// <remarks><p>
179 + /// Win32 MSI API:
180 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
181 + /// </p></remarks>
182 + public void ClearUrlSources()
183 + {
184 + this.ClearSourceType(NativeMethods.SourceType.Url);
185 + }
186 +
187 + /// <summary>
188 + /// Checks if the specified source exists in the list.
189 + /// </summary>
190 + /// <param name="item">case-insensitive source to look for</param>
191 + /// <returns>true if the source exists in the list, false otherwise</returns>
192 + public bool Contains(string item)
193 + {
194 + if (String.IsNullOrEmpty(item))
195 + {
196 + throw new ArgumentNullException("item");
197 + }
198 +
199 + foreach (string s in this)
200 + {
201 + if (s.Equals(item, StringComparison.OrdinalIgnoreCase))
202 + {
203 + return true;
204 + }
205 + }
206 + return false;
207 + }
208 +
209 + /// <summary>
210 + /// Copies the network and URL sources from this list into an array.
211 + /// </summary>
212 + /// <param name="array">destination array to be filed</param>
213 + /// <param name="arrayIndex">offset into the destination array where copying begins</param>
214 + public void CopyTo(string[] array, int arrayIndex)
215 + {
216 + foreach (string source in this)
217 + {
218 + array[arrayIndex++] = source;
219 + }
220 + }
221 +
222 + /// <summary>
223 + /// Removes a network or URL source.
224 + /// </summary>
225 + /// <remarks><p>
226 + /// Win32 MSI API:
227 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearsource.asp">MsiSourceListClearSource</a>
228 + /// </p></remarks>
229 + public bool Remove(string item)
230 + {
231 + if (String.IsNullOrEmpty(item))
232 + {
233 + throw new ArgumentNullException("item");
234 + }
235 +
236 + NativeMethods.SourceType type = item.Contains("://") ? NativeMethods.SourceType.Url : NativeMethods.SourceType.Network;
237 +
238 + uint ret = NativeMethods.MsiSourceListClearSource(
239 + this.installation.InstallationCode,
240 + this.installation.UserSid,
241 + this.installation.Context,
242 + (uint) type | (uint) this.installation.InstallationType,
243 + item);
244 + if (ret != 0)
245 + {
246 + // TODO: Figure out when to return false.
247 + throw InstallerException.ExceptionFromReturnCode(ret);
248 + }
249 + return true;
250 + }
251 +
252 + /// <summary>
253 + /// Enumerates the network and URL sources in the source list of the patch or product installation.
254 + /// </summary>
255 + /// <remarks><p>
256 + /// Win32 MSI API:
257 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenumsources.asp">MsiSourceListEnumSources</a>
258 + /// </p></remarks>
259 + public IEnumerator<string> GetEnumerator()
260 + {
261 + StringBuilder sourceBuf = new StringBuilder(256);
262 + uint sourceBufSize = (uint) sourceBuf.Capacity;
263 + for (uint i = 0; true; i++)
264 + {
265 + uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Network);
266 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
267 + {
268 + break;
269 + }
270 + else if (ret != 0)
271 + {
272 + throw InstallerException.ExceptionFromReturnCode(ret);
273 + }
274 + else
275 + {
276 + yield return sourceBuf.ToString();
277 + }
278 + }
279 +
280 + for (uint i = 0; true; i++)
281 + {
282 + uint ret = this.EnumSources(sourceBuf, i, NativeMethods.SourceType.Url);
283 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
284 + {
285 + break;
286 + }
287 + else if (ret != 0)
288 + {
289 + throw InstallerException.ExceptionFromReturnCode(ret);
290 + }
291 + else
292 + {
293 + yield return sourceBuf.ToString();
294 + }
295 + }
296 + }
297 +
298 + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
299 + {
300 + return this.GetEnumerator();
301 + }
302 +
303 + /// <summary>
304 + /// Forces the installer to search the source list for a valid
305 + /// source the next time a source is required. For example, when the
306 + /// installer performs an installation or reinstallation, or when it
307 + /// requires the path for a component that is set to run from source.
308 + /// </summary>
309 + /// <remarks><p>
310 + /// Win32 MSI APIs:
311 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolution.asp">MsiSourceListForceResolution</a>,
312 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistforceresolutionex.asp">MsiSourceListForceResolutionEx</a>
313 + /// </p></remarks>
314 + public void ForceResolution()
315 + {
316 + uint ret = NativeMethods.MsiSourceListForceResolutionEx(
317 + this.installation.InstallationCode,
318 + this.installation.UserSid,
319 + this.installation.Context,
320 + (uint) this.installation.InstallationType);
321 + if (ret != 0)
322 + {
323 + throw InstallerException.ExceptionFromReturnCode(ret);
324 + }
325 + }
326 +
327 + /// <summary>
328 + /// Gets or sets the path relative to the root of the installation media.
329 + /// </summary>
330 + public string MediaPackagePath
331 + {
332 + get
333 + {
334 + return this["MediaPackagePath"];
335 + }
336 + set
337 + {
338 + this["MediaPackagePath"] = value;
339 + }
340 + }
341 +
342 + /// <summary>
343 + /// Gets or sets the prompt template that is used when prompting the user
344 + /// for installation media.
345 + /// </summary>
346 + public string DiskPrompt
347 + {
348 + get
349 + {
350 + return this["DiskPrompt"];
351 + }
352 + set
353 + {
354 + this["DiskPrompt"] = value;
355 + }
356 + }
357 +
358 + /// <summary>
359 + /// Gets or sets the most recently used source location for the product.
360 + /// </summary>
361 + public string LastUsedSource
362 + {
363 + get
364 + {
365 + return this["LastUsedSource"];
366 + }
367 + set
368 + {
369 + this["LastUsedSource"] = value;
370 + }
371 + }
372 +
373 + /// <summary>
374 + /// Gets or sets the name of the Windows Installer package or patch package
375 + /// on the source.
376 + /// </summary>
377 + public string PackageName
378 + {
379 + get
380 + {
381 + return this["PackageName"];
382 + }
383 + set
384 + {
385 + this["PackageName"] = value;
386 + }
387 + }
388 +
389 + /// <summary>
390 + /// Gets the type of the last-used source.
391 + /// </summary>
392 + /// <remarks><p>
393 + /// <ul>
394 + /// <li>&quot;n&quot; = network location</li>
395 + /// <li>&quot;u&quot; = URL location</li>
396 + /// <li>&quot;m&quot; = media location</li>
397 + /// <li>(empty string) = no last used source</li>
398 + /// </ul>
399 + /// </p></remarks>
400 + public string LastUsedType
401 + {
402 + get
403 + {
404 + return this["LastUsedType"];
405 + }
406 + }
407 +
408 + /// <summary>
409 + /// Gets or sets source list information properties of a product or patch installation.
410 + /// </summary>
411 + /// <param name="property">The source list information property name.</param>
412 + /// <exception cref="ArgumentOutOfRangeException">An unknown product, patch, or property was requested</exception>
413 + /// <remarks><p>
414 + /// Win32 MSI API:
415 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistgetinfo.asp">MsiSourceListGetInfo</a>
416 + /// </p></remarks>
417 + public string this[string property]
418 + {
419 + get
420 + {
421 + StringBuilder buf = new StringBuilder("");
422 + uint bufSize = 0;
423 + uint ret = NativeMethods.MsiSourceListGetInfo(
424 + this.installation.InstallationCode,
425 + this.installation.UserSid,
426 + this.installation.Context,
427 + (uint) this.installation.InstallationType,
428 + property,
429 + buf,
430 + ref bufSize);
431 + if (ret != 0)
432 + {
433 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
434 + {
435 + buf.Capacity = (int) ++bufSize;
436 + ret = NativeMethods.MsiSourceListGetInfo(
437 + this.installation.InstallationCode,
438 + this.installation.UserSid,
439 + this.installation.Context,
440 + (uint) this.installation.InstallationType,
441 + property,
442 + buf,
443 + ref bufSize);
444 + }
445 +
446 + if (ret != 0)
447 + {
448 + if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT ||
449 + ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY)
450 + {
451 + throw new ArgumentOutOfRangeException("property");
452 + }
453 + else
454 + {
455 + throw InstallerException.ExceptionFromReturnCode(ret);
456 + }
457 + }
458 + }
459 + return buf.ToString();
460 + }
461 + set
462 + {
463 + uint ret = NativeMethods.MsiSourceListSetInfo(
464 + this.installation.InstallationCode,
465 + this.installation.UserSid,
466 + this.installation.Context,
467 + (uint) this.installation.InstallationType,
468 + property,
469 + value);
470 + if (ret != 0)
471 + {
472 + if (ret == (uint) NativeMethods.Error.UNKNOWN_PRODUCT ||
473 + ret == (uint) NativeMethods.Error.UNKNOWN_PROPERTY)
474 + {
475 + throw new ArgumentOutOfRangeException("property");
476 + }
477 + else
478 + {
479 + throw InstallerException.ExceptionFromReturnCode(ret);
480 + }
481 + }
482 + }
483 + }
484 +
485 + private void ClearSourceType(NativeMethods.SourceType type)
486 + {
487 + uint ret = NativeMethods.MsiSourceListClearAllEx(
488 + this.installation.InstallationCode,
489 + this.installation.UserSid,
490 + this.installation.Context,
491 + (uint) type | (uint) this.installation.InstallationType);
492 + if (ret != 0)
493 + {
494 + throw InstallerException.ExceptionFromReturnCode(ret);
495 + }
496 + }
497 +
498 + private uint EnumSources(StringBuilder sourceBuf, uint i, NativeMethods.SourceType sourceType)
499 + {
500 + int enumType = (this.installation.InstallationType | (int) sourceType);
501 + uint sourceBufSize = (uint) sourceBuf.Capacity;
502 + uint ret = NativeMethods.MsiSourceListEnumSources(
503 + this.installation.InstallationCode,
504 + this.installation.UserSid,
505 + this.installation.Context,
506 + (uint) enumType,
507 + i,
508 + sourceBuf,
509 + ref sourceBufSize);
510 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
511 + {
512 + sourceBuf.Capacity = (int) ++sourceBufSize;
513 + ret = NativeMethods.MsiSourceListEnumSources(
514 + this.installation.InstallationCode,
515 + this.installation.UserSid,
516 + this.installation.Context,
517 + (uint) enumType,
518 + i,
519 + sourceBuf,
520 + ref sourceBufSize);
521 + }
522 + return ret;
523 + }
524 + }
525 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/SourceMediaList.cs new
+229
@@ -0,0 +1,229 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Text;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// A list of source media for an installed product or patch.
13 + /// </summary>
14 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
15 + public class SourceMediaList : ICollection<MediaDisk>
16 + {
17 + private Installation installation;
18 +
19 + internal SourceMediaList(Installation installation)
20 + {
21 + this.installation = installation;
22 + }
23 +
24 + /// <summary>
25 + /// Gets the number of source media in the list.
26 + /// </summary>
27 + public int Count
28 + {
29 + get
30 + {
31 + int count = 0;
32 + IEnumerator<MediaDisk> e = this.GetEnumerator();
33 + while (e.MoveNext())
34 + {
35 + count++;
36 + }
37 + return count;
38 + }
39 + }
40 +
41 + /// <summary>
42 + /// Gets a boolean value indicating whether the list is read-only.
43 + /// A SourceMediaList is never read-only.
44 + /// </summary>
45 + /// <value>read-only status of the list</value>
46 + public bool IsReadOnly
47 + {
48 + get
49 + {
50 + return false;
51 + }
52 + }
53 +
54 + /// <summary>
55 + /// Adds or updates a disk of the media source for the product or patch.
56 + /// </summary>
57 + /// <remarks><p>
58 + /// Win32 MSI API:
59 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistaddmediadisk.asp">MsiSourceListAddMediaDisk</a>
60 + /// </p></remarks>
61 + public void Add(MediaDisk item)
62 + {
63 + uint ret = NativeMethods.MsiSourceListAddMediaDisk(
64 + this.installation.InstallationCode,
65 + this.installation.UserSid,
66 + this.installation.Context,
67 + (uint) this.installation.InstallationType,
68 + (uint) item.DiskId,
69 + item.VolumeLabel,
70 + item.DiskPrompt);
71 +
72 + if (ret != 0)
73 + {
74 + throw InstallerException.ExceptionFromReturnCode(ret);
75 + }
76 + }
77 +
78 + /// <summary>
79 + /// Removes all source media from the list.
80 + /// </summary>
81 + /// <remarks><p>
82 + /// Win32 MSI API:
83 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearallex.asp">MsiSourceListClearAllEx</a>
84 + /// </p></remarks>
85 + public void Clear()
86 + {
87 + uint ret = NativeMethods.MsiSourceListClearAllEx(
88 + this.installation.InstallationCode,
89 + this.installation.UserSid,
90 + this.installation.Context,
91 + (uint) NativeMethods.SourceType.Media | (uint) this.installation.InstallationType);
92 +
93 + if (ret != 0)
94 + {
95 + throw InstallerException.ExceptionFromReturnCode(ret);
96 + }
97 + }
98 +
99 + /// <summary>
100 + /// Checks if the specified media disk id exists in the list.
101 + /// </summary>
102 + /// <param name="diskId">disk id of the media to look for</param>
103 + /// <returns>true if the media exists in the list, false otherwise</returns>
104 + public bool Contains(int diskId)
105 + {
106 + foreach (MediaDisk mediaDisk in this)
107 + {
108 + if (mediaDisk.DiskId == diskId)
109 + {
110 + return true;
111 + }
112 + }
113 + return false;
114 + }
115 +
116 + bool ICollection<MediaDisk>.Contains(MediaDisk mediaDisk)
117 + {
118 + return this.Contains(mediaDisk.DiskId);
119 + }
120 +
121 + /// <summary>
122 + /// Copies the source media info from this list into an array.
123 + /// </summary>
124 + /// <param name="array">destination array to be filed</param>
125 + /// <param name="arrayIndex">offset into the destination array where copying begins</param>
126 + public void CopyTo(MediaDisk[] array, int arrayIndex)
127 + {
128 + foreach (MediaDisk mediaDisk in this)
129 + {
130 + array[arrayIndex++] = mediaDisk;
131 + }
132 + }
133 +
134 + /// <summary>
135 + /// Removes a specified disk from the set of registered disks.
136 + /// </summary>
137 + /// <param name="diskId">ID of the disk to remove</param>
138 + /// <remarks><p>
139 + /// Win32 MSI API:
140 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistclearmediadisk.asp">MsiSourceListClearMediaDisk</a>
141 + /// </p></remarks>
142 + public bool Remove(int diskId)
143 + {
144 + uint ret = NativeMethods.MsiSourceListClearMediaDisk(
145 + this.installation.InstallationCode,
146 + this.installation.UserSid,
147 + this.installation.Context,
148 + (uint) this.installation.InstallationType,
149 + (uint) diskId);
150 +
151 + if (ret != 0)
152 + {
153 + // TODO: Figure out when to return false.
154 + throw InstallerException.ExceptionFromReturnCode(ret);
155 + }
156 + return true;
157 + }
158 +
159 + bool ICollection<MediaDisk>.Remove(MediaDisk mediaDisk)
160 + {
161 + return this.Remove(mediaDisk.DiskId);
162 + }
163 +
164 + /// <summary>
165 + /// Enumerates the source media in the source list of the patch or product installation.
166 + /// </summary>
167 + /// <remarks><p>
168 + /// Win32 MSI API:
169 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisourcelistenummediadisks.asp">MsiSourceListEnumMediaDisks</a>
170 + /// </p></remarks>
171 + public IEnumerator<MediaDisk> GetEnumerator()
172 + {
173 + uint diskId;
174 + StringBuilder volumeBuf = new StringBuilder(40);
175 + uint volumeBufSize = (uint) volumeBuf.Capacity;
176 + StringBuilder promptBuf = new StringBuilder(80);
177 + uint promptBufSize = (uint) promptBuf.Capacity;
178 + for (uint i = 0; true; i++)
179 + {
180 + uint ret = NativeMethods.MsiSourceListEnumMediaDisks(
181 + this.installation.InstallationCode,
182 + this.installation.UserSid,
183 + this.installation.Context,
184 + (uint) this.installation.InstallationType,
185 + i,
186 + out diskId,
187 + volumeBuf,
188 + ref volumeBufSize,
189 + promptBuf,
190 + ref promptBufSize);
191 +
192 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
193 + {
194 + volumeBuf.Capacity = (int) ++volumeBufSize;
195 + promptBuf.Capacity = (int) ++promptBufSize;
196 +
197 + ret = NativeMethods.MsiSourceListEnumMediaDisks(
198 + this.installation.InstallationCode,
199 + this.installation.UserSid,
200 + this.installation.Context,
201 + (uint) this.installation.InstallationType,
202 + i,
203 + out diskId,
204 + volumeBuf,
205 + ref volumeBufSize,
206 + promptBuf,
207 + ref promptBufSize);
208 + }
209 +
210 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
211 + {
212 + break;
213 + }
214 +
215 + if (ret != 0)
216 + {
217 + throw InstallerException.ExceptionFromReturnCode(ret);
218 + }
219 +
220 + yield return new MediaDisk((int) diskId, volumeBuf.ToString(), promptBuf.ToString());
221 + }
222 + }
223 +
224 + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
225 + {
226 + return this.GetEnumerator();
227 + }
228 + }
229 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/SummaryInfo.cs new
+612
@@ -0,0 +1,612 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Globalization;
9 + using System.Runtime.InteropServices;
10 +
11 + /// <summary>
12 + /// Provides access to summary information of a Windows Installer database.
13 + /// </summary>
14 + public class SummaryInfo : InstallerHandle
15 + {
16 + internal const int MAX_PROPERTIES = 20;
17 +
18 + /// <summary>
19 + /// Gets a SummaryInfo object that can be used to examine, update, and add
20 + /// properties to the summary information stream of a package or transform.
21 + /// </summary>
22 + /// <param name="packagePath">Path to the package (database) or transform</param>
23 + /// <param name="enableWrite">True to reserve resources for writing summary information properties.</param>
24 + /// <exception cref="FileNotFoundException">the package does not exist or could not be read</exception>
25 + /// <exception cref="InstallerException">the package is an invalid format</exception>
26 + /// <remarks><p>
27 + /// The SummaryInfo object should be <see cref="InstallerHandle.Close"/>d after use.
28 + /// It is best that the handle be closed manually as soon as it is no longer
29 + /// needed, as leaving lots of unused handles open can degrade performance.
30 + /// </p><p>
31 + /// Win32 MSI API:
32 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetsummaryinformation.asp">MsiGetSummaryInformation</a>
33 + /// </p></remarks>
34 + public SummaryInfo(string packagePath, bool enableWrite)
35 + : base((IntPtr) SummaryInfo.OpenSummaryInfo(packagePath, enableWrite), true)
36 + {
37 + }
38 +
39 + internal SummaryInfo(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle)
40 + {
41 + }
42 +
43 + /// <summary>Gets or sets the Title summary information property.</summary>
44 + /// <remarks><p>
45 + /// The Title summary information property briefly describes the type of installer package. Phrases
46 + /// such as "Installation Database" or "Transform" or "Patch" may be used for this property.
47 + /// </p><p>
48 + /// Win32 MSI APIs:
49 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
50 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
51 + /// </p></remarks>
52 + public string Title
53 + {
54 + get { return this[2]; }
55 + set { this[2] = value; }
56 + }
57 +
58 + /// <summary>Gets or sets the Subject summary information property.</summary>
59 + /// <remarks><p>
60 + /// The Subject summary information property conveys to a file browser the product that can be installed using
61 + /// the logic and data in this installer database. For example, the value of the summary property for
62 + /// Microsoft Office 97 would be "Microsoft Office 97 Professional". This value is typically set from the
63 + /// installer property ProductName.
64 + /// </p><p>
65 + /// Win32 MSI APIs:
66 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
67 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
68 + /// </p></remarks>
69 + public string Subject
70 + {
71 + get { return this[3]; }
72 + set { this[3] = value; }
73 + }
74 +
75 + /// <summary>Gets or sets the Author summary information property.</summary>
76 + /// <remarks><p>
77 + /// The Author summary information property conveys to a file browser the manufacturer of the installation
78 + /// database. This value is typically set from the installer property Manufacturer.
79 + /// </p><p>
80 + /// Win32 MSI APIs:
81 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
82 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
83 + /// </p></remarks>
84 + public string Author
85 + {
86 + get { return this[4]; }
87 + set { this[4] = value; }
88 + }
89 +
90 + /// <summary>Gets or sets the Keywords summary information property.</summary>
91 + /// <remarks><p>
92 + /// The Keywords summary information property is used by file browsers to hold keywords that permit the
93 + /// database file to be found in a keyword search. The set of keywords typically includes "Installer" as
94 + /// well as product-specific keywords, and may be localized.
95 + /// </p><p>
96 + /// Win32 MSI APIs:
97 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
98 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
99 + /// </p></remarks>
100 + public string Keywords
101 + {
102 + get { return this[5]; }
103 + set { this[5] = value; }
104 + }
105 +
106 + /// <summary>Gets or sets the Comments summary information property.</summary>
107 + /// <remarks><p>
108 + /// The Comments summary information property conveys the general purpose of the installer database. By convention,
109 + /// the value for this summary property is set to the following:
110 + /// </p><p>
111 + /// "This installer database contains the logic and data required to install &lt;product name&gt;."
112 + /// </p><p>
113 + /// where &lt;product name&gt; is the name of the product being installed. In general the value for this summary
114 + /// property only changes in the product name, nothing else.
115 + /// </p><p>
116 + /// Win32 MSI APIs:
117 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
118 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
119 + /// </p></remarks>
120 + public string Comments
121 + {
122 + get { return this[6]; }
123 + set { this[6] = value; }
124 + }
125 +
126 + /// <summary>Gets or sets the Template summary information property.</summary>
127 + /// <remarks><p>
128 + /// The Template summary information propery indicates the platform and language versions supported by the database.
129 + /// </p><p>
130 + /// The syntax of the Template Summary property information is:
131 + /// [platform property][,platform property][,...];[language id][,language id][,...]
132 + /// </p><p>
133 + /// For example, the following are all valid values for the Template Summary property:
134 + /// <list type="bullet">
135 + /// <item>Intel;1033</item>
136 + /// <item>Intel64;1033</item>
137 + /// <item>;1033</item>
138 + /// <item>;</item>
139 + /// <item>Intel ;1033,2046</item>
140 + /// <item>Intel64;1033,2046</item>
141 + /// <item>Intel;0</item>
142 + /// </list>
143 + /// </p><p>
144 + /// If this is a 64-bit Windows Installer, enter Intel64 in the Template summary information property. Note that an
145 + /// installation package cannot have both the Intel and Intel64 properties set.
146 + /// </p><p>
147 + /// If the current platform does not match one of the platforms specified then the installer will not process the
148 + /// package. Not specifying a platform implies that the package is platform-independent.
149 + /// </p><p>
150 + /// Entering 0 in the language ID field of the Template summary information property, or leaving this field empty,
151 + /// indicates that the package is language neutral.
152 + /// </p><p>
153 + /// There are variations of this property depending on whether it is in a source installer database or a transform.
154 + /// </p><p>
155 + /// Source Installer Database - Only one language can be specified in a source installer database. Merge Modules are
156 + /// the only packages that may have multiple languages. For more information, see Multiple Language Merge Modules.
157 + /// </p><p>
158 + /// Transform - In a transform file, only one language may be specified. The specified platform and language determine
159 + /// whether a transform can be applied to a particular database. The platform property and the language property can
160 + /// be left blank if no transform restriction relies on them to validate the transform.
161 + /// </p><p>
162 + /// This summary property is REQUIRED.
163 + /// </p><p>
164 + /// Win32 MSI APIs:
165 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
166 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
167 + /// </p></remarks>
168 + public string Template
169 + {
170 + get { return this[7]; }
171 + set { this[7] = value; }
172 + }
173 +
174 + /// <summary>Gets or sets the LastSavedBy summary information property.</summary>
175 + /// <remarks><p>
176 + /// The installer sets the Last Saved By summary information property to the value of the LogonUser property during
177 + /// an administrative installation. The installer never uses this property and a user never needs to modify it.
178 + /// Developers of a database editing tool may use this property to track the last person to modify the database.
179 + /// This property should be left set to null in a final shipping database.
180 + /// </p><p>
181 + /// In a transform, this summary property contains the platform and language ID(s) that a database should have
182 + /// after it has been transformed. The property specifies to what the Template should be set in the new database.
183 + /// </p><p>
184 + /// Win32 MSI APIs:
185 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
186 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
187 + /// </p></remarks>
188 + public string LastSavedBy
189 + {
190 + get { return this[8]; }
191 + set { this[8] = value; }
192 + }
193 +
194 + /// <summary>Gets or sets the RevisionNumber summary information property.</summary>
195 + /// <remarks><p>
196 + /// The Revision Number summary information property contains the package code for the installer package. The
197 + /// package code is a unique identifier of the installer package.
198 + /// </p><p>
199 + /// The Revision Number summary information property of a patch package specifies the GUID patch code for
200 + /// the patch. This is followed by a list of patch code GUIDs for obsolete patches that are removed when this
201 + /// patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list.
202 + /// </p><p>
203 + /// The Revision Number summary information property of a transform package lists the product code GUIDs
204 + /// and version of the new and original products and the upgrade code GUID. The list is separated with
205 + /// semicolons as follows.
206 + /// </p><p>
207 + /// Original-Product-Code Original-Product-Version ; New-Product Code New-Product-Version; Upgrade-Code
208 + /// </p><p>
209 + /// This summary property is REQUIRED.
210 + /// </p><p>
211 + /// Win32 MSI APIs:
212 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
213 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
214 + /// </p></remarks>
215 + public string RevisionNumber
216 + {
217 + get { return this[9]; }
218 + set { this[9] = value; }
219 + }
220 +
221 + /// <summary>Gets or sets the CreatingApp summary information property.</summary>
222 + /// <remarks><p>
223 + /// The CreatingApp summary information property conveys which application created the installer database.
224 + /// In general the value for this summary property is the name of the software used to author this database.
225 + /// </p><p>
226 + /// Win32 MSI APIs:
227 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
228 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
229 + /// </p></remarks>
230 + public string CreatingApp
231 + {
232 + get { return this[18]; }
233 + set { this[18] = value; }
234 + }
235 +
236 + /// <summary>Gets or sets the LastPrintTime summary information property.</summary>
237 + /// <remarks><p>
238 + /// The LastPrintTime summary information property can be set to the date and time during an administrative
239 + /// installation to record when the administrative image was created. For non-administrative installations
240 + /// this property is the same as the CreateTime summary information property.
241 + /// </p><p>
242 + /// Win32 MSI APIs:
243 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
244 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
245 + /// </p></remarks>
246 + public DateTime LastPrintTime
247 + {
248 + get { return (DateTime) this[11, typeof(DateTime)]; }
249 + set { this[11, typeof(DateTime)] = value; }
250 + }
251 +
252 + /// <summary>Gets or sets the CreateTime summary information property.</summary>
253 + /// <remarks><p>
254 + /// The CreateTime summary information property conveys when the installer database was created.
255 + /// </p><p>
256 + /// Win32 MSI APIs:
257 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
258 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
259 + /// </p></remarks>
260 + public DateTime CreateTime
261 + {
262 + get { return (DateTime) this[12, typeof(DateTime)]; }
263 + set { this[12, typeof(DateTime)] = value; }
264 + }
265 +
266 + /// <summary>Gets or sets the LastSaveTime summary information property.</summary>
267 + /// <remarks><p>
268 + /// The LastSaveTime summary information property conveys when the last time the installer database was
269 + /// modified. Each time a user changes an installation the value for this summary property is updated to
270 + /// the current system time/date at the time the installer database was saved. Initially the value for
271 + /// this summary property is set to null to indicate that no changes have yet been made.
272 + /// </p><p>
273 + /// Win32 MSI APIs:
274 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
275 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
276 + /// </p></remarks>
277 + public DateTime LastSaveTime
278 + {
279 + get { return (DateTime) this[13, typeof(DateTime)]; }
280 + set { this[13, typeof(DateTime)] = value; }
281 + }
282 +
283 + /// <summary>Gets or sets the CodePage summary information property.</summary>
284 + /// <remarks><p>
285 + /// The Codepage summary information property is the numeric value of the ANSI code page used for any
286 + /// strings that are stored in the summary information. Note that this is not the same code page for
287 + /// strings in the installation database. The Codepage summary information property is used to translate
288 + /// the strings in the summary information into Unicode when calling the Unicode API functions. The
289 + /// Codepage summary information property must be set before any string properties are set in the
290 + /// summary information.
291 + /// </p><p>
292 + /// Win32 MSI APIs:
293 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
294 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
295 + /// </p></remarks>
296 + public short CodePage
297 + {
298 + get { return (short) this[1, typeof(short)]; }
299 + set { this[1, typeof(short)] = value; }
300 + }
301 +
302 + /// <summary>Gets or sets the PageCount summary information property.</summary>
303 + /// <remarks><p>
304 + /// For an installation package, the PageCount summary information property contains the minimum
305 + /// installer version required. For Windows Installer version 1.0, this property must be set to the
306 + /// integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200.
307 + /// </p><p>
308 + /// For a transform package, the PageCount summary information property contains minimum installer
309 + /// version required to process the transform. Set to the greater of the two PageCount summary information
310 + /// property values belonging to the databases used to generate the transform.
311 + /// </p><p>
312 + /// The PageCount summary information property is set to null in patch packages.
313 + /// </p><p>
314 + /// This summary property is REQUIRED.
315 + /// </p><p>
316 + /// Win32 MSI APIs:
317 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
318 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
319 + /// </p></remarks>
320 + public int PageCount
321 + {
322 + get { return (int) this[14, typeof(int)]; }
323 + set { this[14, typeof(int)] = value; }
324 + }
325 +
326 + /// <summary>Gets or sets the WordCount summary information property.</summary>
327 + /// <remarks><p>
328 + /// The WordCount summary information property indicates the type of source file image. If this property is
329 + /// not present, it defaults to 0. Note that this property is stored in place of the standard Count property.
330 + /// </p><p>
331 + /// This property is a bit field. New bits may be added in the future. At present the following bits are
332 + /// available:
333 + /// <list type="bullet">
334 + /// <item>Bit 0: 0 = long file names, 1 = short file names</item>
335 + /// <item>Bit 1: 0 = source is uncompressed, 1 = source is compressed</item>
336 + /// <item>Bit 2: 0 = source is original media, 1 = source is administrative installation</item>
337 + /// <item>[MSI 4.0] Bit 3: 0 = elevated privileges can be required to install, 1 = elevated privileges are not required to install</item>
338 + /// </list>
339 + /// </p><p>
340 + /// These are combined to give the WordCount summary information property one of the following values
341 + /// indicating a type of source file image:
342 + /// <list type="bullet">
343 + /// <item>0 - Original source using long file names. Matches tree in Directory table.</item>
344 + /// <item>1 - Original source using short file names. Matches tree in Directory table.</item>
345 + /// <item>2 - Compressed source files using long file names. Matches cabinets and files in the Media table.</item>
346 + /// <item>3 - Compressed source files using short file names. Matches cabinets and files in the Media table.</item>
347 + /// <item>4 - Administrative image using long file names. Matches tree in Directory table.</item>
348 + /// <item>5 - Administrative image using short file names. Matches tree in Directory table.</item>
349 + /// </list>
350 + /// </p><p>
351 + /// Note that if the package is marked as compressed (bit 1 is set), the installer only installs files
352 + /// located at the root of the source. In this case, even files marked as uncompressed in the File table must
353 + /// be located at the root to be installed. To specify a source image that has both a cabinet file (compressed
354 + /// files) and uncompressed files that match the tree in the Directory table, mark the package as uncompressed
355 + /// by leaving bit 1 unset (value=0) in the WordCount summary information property and set
356 + /// <see cref="FileAttributes.Compressed"/> (value=16384) in the Attributes column of the File table
357 + /// for each file in the cabinet.
358 + /// </p><p>
359 + /// For a patch package, the WordCount summary information property specifies the patch engine that was used
360 + /// to create the patch files. The default value is 1 and indicates that MSPATCH was used to create the patch
361 + /// A value of "2" means that the patch is using smaller, optimized, files available only with Windows Installer
362 + /// version 1.2 or later. A patch with a WordCount of "2" fails immediately if used with a Windows Installer
363 + /// version earlier than 1.2. A patch with a WordCount of "3" fails immediately if used with a Windows Installer
364 + /// version earlier than 2.0.
365 + /// </p><p>
366 + /// This summary property is REQUIRED.
367 + /// </p><p>
368 + /// Win32 MSI APIs:
369 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
370 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
371 + /// </p></remarks>
372 + public int WordCount
373 + {
374 + get { return (int) this[15, typeof(int)]; }
375 + set { this[15, typeof(int)] = value; }
376 + }
377 +
378 + /// <summary>Gets or sets the CharacterCount summary information property.</summary>
379 + /// <remarks><p>
380 + /// The CharacterCount summary information property is only used in transforms. This part of the summary
381 + /// information stream is divided into two 16-bit words. The upper word contains the transform validation
382 + /// flags. The lower word contains the transform error condition flags.
383 + /// </p><p>
384 + /// Win32 MSI APIs:
385 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
386 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
387 + /// </p></remarks>
388 + public int CharacterCount
389 + {
390 + get { return (int) this[16, typeof(int)]; }
391 + set { this[16, typeof(int)] = value; }
392 + }
393 +
394 + /// <summary>Gets or sets the Security summary information property.</summary>
395 + /// <remarks><p>
396 + /// The Security summary information property conveys whether the package should be opened as read-only. The database
397 + /// editing tool should not modify a read-only enforced database and should issue a warning at attempts to modify a
398 + /// read-only recommended database. The following values of this property are applicable to Windows Installer files:
399 + /// <list type="bullet">
400 + /// <item>0 - no restriction</item>
401 + /// <item>2 - read only recommended</item>
402 + /// <item>4 - read only enforced</item>
403 + /// </list>
404 + /// </p><p>
405 + /// This property should be set to read-only recommended (2) for an installation database and to read-only
406 + /// enforced (4) for a transform or patch.
407 + /// </p><p>
408 + /// Win32 MSI APIs:
409 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfogetproperty.asp">MsiSummaryInfoGetProperty</a>,
410 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfosetproperty.asp">MsiSummaryInfoSetProperty</a>
411 + /// </p></remarks>
412 + public int Security
413 + {
414 + get { return (int) this[19, typeof(int)]; }
415 + set { this[19, typeof(int)] = value; }
416 + }
417 +
418 + private object this[uint property, Type type]
419 + {
420 + get
421 + {
422 + uint dataType;
423 + StringBuilder stringValue = new StringBuilder("");
424 + uint bufSize = 0;
425 + int intValue;
426 + long timeValue = 0;
427 +
428 + uint ret = RemotableNativeMethods.MsiSummaryInfoGetProperty(
429 + (int) this.Handle,
430 + property,
431 + out dataType,
432 + out intValue,
433 + ref timeValue,
434 + stringValue,
435 + ref bufSize);
436 + if (ret != 0 && dataType != (uint) VarEnum.VT_LPSTR)
437 + {
438 + throw InstallerException.ExceptionFromReturnCode(ret);
439 + }
440 +
441 + switch ((VarEnum) dataType)
442 + {
443 + case VarEnum.VT_EMPTY:
444 + {
445 + if (type == typeof(DateTime))
446 + {
447 + return DateTime.MinValue;
448 + }
449 + else if (type == typeof(string))
450 + {
451 + return String.Empty;
452 + }
453 + else if (type == typeof(short))
454 + {
455 + return (short) 0;
456 + }
457 + else
458 + {
459 + return (int) 0;
460 + }
461 + }
462 +
463 + case VarEnum.VT_LPSTR:
464 + {
465 + if (ret == (uint) NativeMethods.Error.MORE_DATA)
466 + {
467 + stringValue.Capacity = (int) ++bufSize;
468 + ret = RemotableNativeMethods.MsiSummaryInfoGetProperty(
469 + (int) this.Handle,
470 + property,
471 + out dataType,
472 + out intValue,
473 + ref timeValue,
474 + stringValue,
475 + ref bufSize);
476 + }
477 + if (ret != 0)
478 + {
479 + throw InstallerException.ExceptionFromReturnCode(ret);
480 + }
481 + return stringValue.ToString();
482 + }
483 +
484 + case VarEnum.VT_I2:
485 + case VarEnum.VT_I4:
486 + {
487 + if (type == typeof(string))
488 + {
489 + return intValue.ToString(CultureInfo.InvariantCulture);
490 + }
491 + else if (type == typeof(short))
492 + {
493 + return (short) intValue;
494 + }
495 + else
496 + {
497 + return intValue;
498 + }
499 + }
500 +
501 + case VarEnum.VT_FILETIME:
502 + {
503 + if (type == typeof(string))
504 + {
505 + return DateTime.FromFileTime(timeValue).ToString(CultureInfo.InvariantCulture);
506 + }
507 + else
508 + {
509 + return DateTime.FromFileTime(timeValue);
510 + }
511 + }
512 +
513 + default:
514 + {
515 + throw new InstallerException();
516 + }
517 + }
518 + }
519 +
520 + set
521 + {
522 + uint dataType = (uint) VarEnum.VT_NULL;
523 + string stringValue = "";
524 + int intValue = 0;
525 + long timeValue = 0;
526 +
527 + if (type == typeof(short))
528 + {
529 + dataType = (uint) VarEnum.VT_I2;
530 + intValue = (int)(short) value; // Double cast because value is a *boxed* short.
531 + }
532 + else if (type == typeof(int))
533 + {
534 + dataType = (uint) VarEnum.VT_I4;
535 + intValue = (int) value;
536 + }
537 + else if (type == typeof(string))
538 + {
539 + dataType = (uint) VarEnum.VT_LPSTR;
540 + stringValue = (string) value;
541 + }
542 + else // (type == typeof(DateTime))
543 + {
544 + dataType = (uint) VarEnum.VT_FILETIME;
545 + timeValue = ((DateTime) value).ToFileTime();
546 + }
547 +
548 + uint ret = NativeMethods.MsiSummaryInfoSetProperty(
549 + (int) this.Handle,
550 + property,
551 + dataType,
552 + intValue,
553 + ref timeValue,
554 + stringValue);
555 + if (ret != 0)
556 + {
557 + throw InstallerException.ExceptionFromReturnCode(ret);
558 + }
559 + }
560 + }
561 +
562 + private string this[uint property]
563 + {
564 + get { return (string) this[property, typeof(string)]; }
565 + set { this[property, typeof(string)] = value; }
566 + }
567 +
568 + /// <summary>
569 + /// Formats and writes the previously stored properties into the standard summary information stream.
570 + /// </summary>
571 + /// <exception cref="InstallerException">The stream cannot be successfully written.</exception>
572 + /// <remarks><p>
573 + /// This method may only be called once after all the property values have been set. Properties may
574 + /// still be read after the stream is written.
575 + /// </p><p>
576 + /// Win32 MSI API:
577 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisummaryinfopersist.asp">MsiSummaryInfoPersist</a>
578 + /// </p></remarks>
579 + public void Persist()
580 + {
581 + uint ret = NativeMethods.MsiSummaryInfoPersist((int) this.Handle);
582 + if (ret != 0)
583 + {
584 + throw InstallerException.ExceptionFromReturnCode(ret);
585 + }
586 + }
587 +
588 + private static int OpenSummaryInfo(string packagePath, bool enableWrite)
589 + {
590 + int summaryInfoHandle;
591 + int maxProperties = !enableWrite ? 0 : SummaryInfo.MAX_PROPERTIES;
592 + uint ret = RemotableNativeMethods.MsiGetSummaryInformation(
593 + 0,
594 + packagePath,
595 + (uint) maxProperties,
596 + out summaryInfoHandle);
597 + if (ret != 0)
598 + {
599 + if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND ||
600 + ret == (uint) NativeMethods.Error.ACCESS_DENIED)
601 + {
602 + throw new FileNotFoundException(null, packagePath);
603 + }
604 + else
605 + {
606 + throw InstallerException.ExceptionFromReturnCode(ret);
607 + }
608 + }
609 + return summaryInfoHandle;
610 + }
611 + }
612 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/TableCollection.cs new
+192
@@ -0,0 +1,192 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Text;
8 +
9 + /// <summary>
10 + /// Contains information about all the tables in a Windows Installer database.
11 + /// </summary>
12 + public class TableCollection : ICollection<TableInfo>
13 + {
14 + private Database db;
15 +
16 + internal TableCollection(Database db)
17 + {
18 + this.db = db;
19 + }
20 +
21 + /// <summary>
22 + /// Gets the number of tables in the database.
23 + /// </summary>
24 + public int Count
25 + {
26 + get
27 + {
28 + return this.GetTables().Count;
29 + }
30 + }
31 +
32 + /// <summary>
33 + /// Gets a boolean value indicating whether the collection is read-only.
34 + /// A TableCollection is read-only when the database is read-only.
35 + /// </summary>
36 + /// <value>read-only status of the collection</value>
37 + public bool IsReadOnly
38 + {
39 + get
40 + {
41 + return this.db.IsReadOnly;
42 + }
43 + }
44 +
45 + /// <summary>
46 + /// Gets information about a given table.
47 + /// </summary>
48 + /// <param name="table">case-sensitive name of the table</param>
49 + /// <returns>information about the requested table, or null if the table does not exist in the database</returns>
50 + public TableInfo this[string table]
51 + {
52 + get
53 + {
54 + if (String.IsNullOrEmpty(table))
55 + {
56 + throw new ArgumentNullException("table");
57 + }
58 +
59 + if (!this.Contains(table))
60 + {
61 + return null;
62 + }
63 +
64 + return new TableInfo(this.db, table);
65 + }
66 + }
67 +
68 + /// <summary>
69 + /// Adds a new table to the database.
70 + /// </summary>
71 + /// <param name="item">information about the table to be added</param>
72 + /// <exception cref="InvalidOperationException">a table with the same name already exists in the database</exception>
73 + public void Add(TableInfo item)
74 + {
75 + if (item == null)
76 + {
77 + throw new ArgumentNullException("item");
78 + }
79 +
80 + if (this.Contains(item.Name))
81 + {
82 + throw new InvalidOperationException();
83 + }
84 +
85 + this.db.Execute(item.SqlCreateString);
86 + }
87 +
88 + /// <summary>
89 + /// Removes all tables (and all data) from the database.
90 + /// </summary>
91 + public void Clear()
92 + {
93 + foreach (string table in this.GetTables())
94 + {
95 + this.Remove(table);
96 + }
97 + }
98 +
99 + /// <summary>
100 + /// Checks if the database contains a table with the given name.
101 + /// </summary>
102 + /// <param name="item">case-sensitive name of the table to search for</param>
103 + /// <returns>True if the table exists, false otherwise.</returns>
104 + public bool Contains(string item)
105 + {
106 + if (String.IsNullOrEmpty(item))
107 + {
108 + throw new ArgumentNullException("item");
109 + }
110 + uint ret = RemotableNativeMethods.MsiDatabaseIsTablePersistent((int) this.db.Handle, item);
111 + if (ret == 3) // MSICONDITION_ERROR
112 + {
113 + throw new InstallerException();
114 + }
115 + return ret != 2; // MSICONDITION_NONE
116 + }
117 +
118 + bool ICollection<TableInfo>.Contains(TableInfo item)
119 + {
120 + return this.Contains(item.Name);
121 + }
122 +
123 + /// <summary>
124 + /// Copies the table information from this collection into an array.
125 + /// </summary>
126 + /// <param name="array">destination array to be filed</param>
127 + /// <param name="arrayIndex">offset into the destination array where copying begins</param>
128 + public void CopyTo(TableInfo[] array, int arrayIndex)
129 + {
130 + if (array == null)
131 + {
132 + throw new ArgumentNullException("array");
133 + }
134 +
135 + foreach (string table in this.GetTables())
136 + {
137 + array[arrayIndex++] = new TableInfo(this.db, table);
138 + }
139 + }
140 +
141 + /// <summary>
142 + /// Removes a table from the database.
143 + /// </summary>
144 + /// <param name="item">case-sensitive name of the table to be removed</param>
145 + /// <returns>true if the table was removed, false if the table did not exist</returns>
146 + public bool Remove(string item)
147 + {
148 + if (String.IsNullOrEmpty(item))
149 + {
150 + throw new ArgumentNullException("item");
151 + }
152 +
153 + if (!this.Contains(item))
154 + {
155 + return false;
156 + }
157 + this.db.Execute("DROP TABLE `{0}`", item);
158 + return true;
159 + }
160 +
161 + bool ICollection<TableInfo>.Remove(TableInfo item)
162 + {
163 + if (item == null)
164 + {
165 + throw new ArgumentNullException("item");
166 + }
167 +
168 + return this.Remove(item.Name);
169 + }
170 +
171 + /// <summary>
172 + /// Enumerates the tables in the database.
173 + /// </summary>
174 + public IEnumerator<TableInfo> GetEnumerator()
175 + {
176 + foreach (string table in this.GetTables())
177 + {
178 + yield return new TableInfo(this.db, table);
179 + }
180 + }
181 +
182 + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
183 + {
184 + return this.GetEnumerator();
185 + }
186 +
187 + private IList<string> GetTables()
188 + {
189 + return this.db.ExecuteStringQuery("SELECT `Name` FROM `_Tables`");
190 + }
191 + }
192 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/TableInfo.cs new
+264
@@ -0,0 +1,264 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Collections.ObjectModel;
9 +
10 + /// <summary>
11 + /// Defines a table in an installation database.
12 + /// </summary>
13 + public class TableInfo
14 + {
15 + private string name;
16 + private ColumnCollection columns;
17 + private ReadOnlyCollection<string> primaryKeys;
18 +
19 + /// <summary>
20 + /// Creates a table definition.
21 + /// </summary>
22 + /// <param name="name">Name of the table.</param>
23 + /// <param name="columns">Columns in the table.</param>
24 + /// <param name="primaryKeys">The primary keys of the table.</param>
25 + public TableInfo(string name, ICollection<ColumnInfo> columns, IList<string> primaryKeys)
26 + {
27 + if (String.IsNullOrEmpty(name))
28 + {
29 + throw new ArgumentNullException("name");
30 + }
31 +
32 + if (columns == null || columns.Count == 0)
33 + {
34 + throw new ArgumentNullException("columns");
35 + }
36 +
37 + if (primaryKeys == null || primaryKeys.Count == 0)
38 + {
39 + throw new ArgumentNullException("primaryKeys");
40 + }
41 +
42 + this.name = name;
43 + this.columns = new ColumnCollection(columns);
44 + this.primaryKeys = new List<string>(primaryKeys).AsReadOnly();
45 + foreach (string primaryKey in this.primaryKeys)
46 + {
47 + if (!this.columns.Contains(primaryKey))
48 + {
49 + throw new ArgumentOutOfRangeException("primaryKeys");
50 + }
51 + }
52 + }
53 +
54 + internal TableInfo(Database db, string name)
55 + {
56 + if (db == null)
57 + {
58 + throw new ArgumentNullException("db");
59 + }
60 +
61 + if (String.IsNullOrEmpty(name))
62 + {
63 + throw new ArgumentNullException("name");
64 + }
65 +
66 + this.name = name;
67 +
68 + using (View columnsView = db.OpenView("SELECT * FROM `{0}`", name))
69 + {
70 + this.columns = new ColumnCollection(columnsView);
71 + }
72 +
73 + this.primaryKeys = new ReadOnlyCollection<string>(
74 + TableInfo.GetTablePrimaryKeys(db, name));
75 + }
76 +
77 + /// <summary>
78 + /// Gets the name of the table.
79 + /// </summary>
80 + public string Name
81 + {
82 + get
83 + {
84 + return this.name;
85 + }
86 + }
87 +
88 + /// <summary>
89 + /// Gets information about the columns in this table.
90 + /// </summary>
91 + /// <remarks><p>
92 + /// This property queries the database every time it is called,
93 + /// to ensure the returned values are up-to-date. For best performance,
94 + /// hold onto the returned collection if using it more than once.
95 + /// </p></remarks>
96 + public ColumnCollection Columns
97 + {
98 + get
99 + {
100 + return this.columns;
101 + }
102 + }
103 +
104 + /// <summary>
105 + /// Gets the names of the columns that are primary keys of the table.
106 + /// </summary>
107 + public IList<string> PrimaryKeys
108 + {
109 + get
110 + {
111 + return this.primaryKeys;
112 + }
113 + }
114 +
115 + /// <summary>
116 + /// Gets an SQL CREATE string that can be used to create the table.
117 + /// </summary>
118 + public string SqlCreateString
119 + {
120 + get
121 + {
122 + StringBuilder s = new StringBuilder("CREATE TABLE `");
123 + s.Append(this.name);
124 + s.Append("` (");
125 + int count = 0;
126 + foreach (ColumnInfo col in this.Columns)
127 + {
128 + if (count > 0)
129 + {
130 + s.Append(", ");
131 + }
132 + s.Append(col.SqlCreateString);
133 + count++;
134 + }
135 + s.Append(" PRIMARY KEY ");
136 + count = 0;
137 + foreach (string key in this.PrimaryKeys)
138 + {
139 + if (count > 0)
140 + {
141 + s.Append(", ");
142 + }
143 + s.AppendFormat("`{0}`", key);
144 + count++;
145 + }
146 + s.Append(')');
147 + return s.ToString();
148 + }
149 + }
150 +
151 + /// <summary>
152 + /// Gets an SQL INSERT string that can be used insert a new record into the table.
153 + /// </summary>
154 + /// <remarks><p>
155 + /// The values are expressed as question-mark tokens, to be supplied by the record.
156 + /// </p></remarks>
157 + public string SqlInsertString
158 + {
159 + get
160 + {
161 + StringBuilder s = new StringBuilder("INSERT INTO `");
162 + s.Append(this.name);
163 + s.Append("` (");
164 + int count = 0;
165 + foreach (ColumnInfo col in this.Columns)
166 + {
167 + if (count > 0)
168 + {
169 + s.Append(", ");
170 + }
171 +
172 + s.AppendFormat("`{0}`", col.Name);
173 + count++;
174 + }
175 + s.Append(") VALUES (");
176 + while (count > 0)
177 + {
178 + count--;
179 + s.Append("?");
180 +
181 + if (count > 0)
182 + {
183 + s.Append(", ");
184 + }
185 + }
186 + s.Append(')');
187 + return s.ToString();
188 + }
189 + }
190 +
191 + /// <summary>
192 + /// Gets an SQL SELECT string that can be used to select all columns of the table.
193 + /// </summary>
194 + /// <remarks><p>
195 + /// The columns are listed explicitly in the SELECT string, as opposed to using "SELECT *".
196 + /// </p></remarks>
197 + public string SqlSelectString
198 + {
199 + get
200 + {
201 + StringBuilder s = new StringBuilder("SELECT ");
202 + int count = 0;
203 + foreach (ColumnInfo col in this.Columns)
204 + {
205 + if (count > 0) s.Append(", ");
206 + s.AppendFormat("`{0}`", col.Name);
207 + count++;
208 + }
209 + s.AppendFormat(" FROM `{0}`", this.Name);
210 + return s.ToString();
211 + }
212 + }
213 +
214 + /// <summary>
215 + /// Gets a string representation of the table.
216 + /// </summary>
217 + /// <returns>The name of the table.</returns>
218 + public override string ToString()
219 + {
220 + return this.name;
221 + }
222 +
223 + private static IList<string> GetTablePrimaryKeys(Database db, string table)
224 + {
225 + if (table == "_Tables")
226 + {
227 + return new string[] { "Name" };
228 + }
229 + else if (table == "_Columns")
230 + {
231 + return new string[] { "Table", "Number" };
232 + }
233 + else if (table == "_Storages")
234 + {
235 + return new string[] { "Name" };
236 + }
237 + else if (table == "_Streams")
238 + {
239 + return new string[] { "Name" };
240 + }
241 + else
242 + {
243 + int hrec;
244 + uint ret = RemotableNativeMethods.MsiDatabaseGetPrimaryKeys(
245 + (int) db.Handle, table, out hrec);
246 + if (ret != 0)
247 + {
248 + throw InstallerException.ExceptionFromReturnCode(ret);
249 + }
250 +
251 + using (Record rec = new Record((IntPtr) hrec, true, null))
252 + {
253 + string[] keys = new string[rec.FieldCount];
254 + for (int i = 0; i < keys.Length; i++)
255 + {
256 + keys[i] = rec.GetString(i + 1);
257 + }
258 +
259 + return keys;
260 + }
261 + }
262 + }
263 + }
264 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/Transaction.cs new
+201
@@ -0,0 +1,201 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Diagnostics.CodeAnalysis;
8 + using System.Runtime.InteropServices;
9 + using System.Threading;
10 +
11 + /// <summary>
12 + /// [MSI 4.5] Handle to a multi-session install transaction.
13 + /// </summary>
14 + /// <remarks><p>
15 + /// Win32 MSI APIs:
16 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a>
17 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a>
18 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
19 + /// </p></remarks>
20 + public class Transaction : InstallerHandle
21 + {
22 + private string name;
23 + private IntPtr ownerChangeEvent;
24 + private IList<EventHandler<EventArgs>> ownerChangeListeners;
25 +
26 + /// <summary>
27 + /// [MSI 4.5] Begins transaction processing of a multi-package installation.
28 + /// </summary>
29 + /// <param name="name">Name of the multi-package installation.</param>
30 + /// <param name="attributes">Select optional behavior when beginning the transaction.</param>
31 + /// <exception cref="InstallerException">The transaction could not be initialized.</exception>
32 + /// <remarks><p>
33 + /// Win32 MSI API:
34 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a>
35 + /// </p></remarks>
36 + public Transaction(string name, TransactionAttributes attributes)
37 + : this(name, Transaction.Begin(name, attributes), true)
38 + {
39 + }
40 +
41 + /// <summary>
42 + /// Internal constructor.
43 + /// </summary>
44 + /// <remarks>
45 + /// The second parameter is an array in order to receive multiple values from the initialization method.
46 + /// </remarks>
47 + private Transaction(string name, IntPtr[] handles, bool ownsHandle)
48 + : base(handles[0], ownsHandle)
49 + {
50 + this.name = name;
51 + this.ownerChangeEvent = handles[1];
52 + this.ownerChangeListeners = new List<EventHandler<EventArgs>>();
53 + }
54 +
55 + /// <summary>
56 + /// Creates a new Transaction object from an integer handle.
57 + /// </summary>
58 + /// <param name="handle">Integer transaction handle</param>
59 + /// <param name="ownsHandle">true to close the handle when this object is disposed</param>
60 + public static Transaction FromHandle(IntPtr handle, bool ownsHandle)
61 + {
62 + return new Transaction(handle.ToString(), new IntPtr[] { handle, IntPtr.Zero }, ownsHandle);
63 + }
64 +
65 + /// <summary>
66 + /// Gets the name of the transaction.
67 + /// </summary>
68 + public string Name
69 + {
70 + get
71 + {
72 + return name;
73 + }
74 + }
75 +
76 + /// <summary>
77 + /// Notifies listeners when the process that owns the transaction changed.
78 + /// </summary>
79 + public event EventHandler<EventArgs> OwnerChanged
80 + {
81 + add
82 + {
83 + this.ownerChangeListeners.Add(value);
84 +
85 + if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count == 1)
86 + {
87 + new Thread(this.WaitForOwnerChange).Start();
88 + }
89 + }
90 + remove
91 + {
92 + this.ownerChangeListeners.Remove(value);
93 + }
94 + }
95 +
96 + private void OnOwnerChanged()
97 + {
98 + EventArgs e = new EventArgs();
99 + foreach (EventHandler<EventArgs> handler in this.ownerChangeListeners)
100 + {
101 + handler(this, e);
102 + }
103 + }
104 +
105 + private void WaitForOwnerChange()
106 + {
107 + int ret = NativeMethods.WaitForSingleObject(this.ownerChangeEvent, -1);
108 + if (ret == 0)
109 + {
110 + this.OnOwnerChanged();
111 + }
112 + else
113 + {
114 + throw new InstallerException();
115 + }
116 + }
117 +
118 + /// <summary>
119 + /// Makes the current process the owner of the multi-package installation transaction.
120 + /// </summary>
121 + /// <param name="attributes">Select optional behavior when joining the transaction.</param>
122 + /// <exception cref="InvalidHandleException">The transaction handle is not valid.</exception>
123 + /// <exception cref="InstallerException">The transaction could not be joined.</exception>
124 + /// <remarks><p>
125 + /// Win32 MSI API:
126 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a>
127 + /// </p></remarks>
128 + public void Join(TransactionAttributes attributes)
129 + {
130 + IntPtr hChangeOfOwnerEvent;
131 + uint ret = NativeMethods.MsiJoinTransaction((int) this.Handle, (int) attributes, out hChangeOfOwnerEvent);
132 + if (ret != 0)
133 + {
134 + throw InstallerException.ExceptionFromReturnCode(ret);
135 + }
136 +
137 + this.ownerChangeEvent = hChangeOfOwnerEvent;
138 + if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count >= 1)
139 + {
140 + new Thread(this.WaitForOwnerChange).Start();
141 + }
142 + }
143 +
144 + /// <summary>
145 + /// Ends the install transaction and commits all changes to the system belonging to the transaction.
146 + /// </summary>
147 + /// <exception cref="InstallerException">The transaction could not be committed.</exception>
148 + /// <remarks><p>
149 + /// Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language
150 + /// runtime assemblies. Deletes the rollback script, and after using this option, the transaction's
151 + /// changes can no longer be undone with a Rollback Installation.
152 + /// </p><p>
153 + /// This method can only be called by the current owner of the transaction.
154 + /// </p><p>
155 + /// Win32 MSI API:
156 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
157 + /// </p></remarks>
158 + public void Commit()
159 + {
160 + this.End(true);
161 + }
162 +
163 + /// <summary>
164 + /// Ends the install transaction and undoes changes to the system belonging to the transaction.
165 + /// </summary>
166 + /// <exception cref="InstallerException">The transaction could not be rolled back.</exception>
167 + /// <remarks><p>
168 + /// This method can only be called by the current owner of the transaction.
169 + /// </p><p>
170 + /// Win32 MSI API:
171 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a>
172 + /// </p></remarks>
173 + public void Rollback()
174 + {
175 + this.End(false);
176 + }
177 +
178 + private static IntPtr[] Begin(string transactionName, TransactionAttributes attributes)
179 + {
180 + int hTransaction;
181 + IntPtr hChangeOfOwnerEvent;
182 + uint ret = NativeMethods.MsiBeginTransaction(transactionName, (int) attributes, out hTransaction, out hChangeOfOwnerEvent);
183 + if (ret != 0)
184 + {
185 + throw InstallerException.ExceptionFromReturnCode(ret);
186 + }
187 +
188 + return new IntPtr[] { (IntPtr) hTransaction, hChangeOfOwnerEvent };
189 + }
190 +
191 + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
192 + private void End(bool commit)
193 + {
194 + uint ret = NativeMethods.MsiEndTransaction(commit ? 1 : 0);
195 + if (ret != 0)
196 + {
197 + throw InstallerException.ExceptionFromReturnCode(ret);
198 + }
199 + }
200 + }
201 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/ValidationErrorInfo.cs new
+46
@@ -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 +
3 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System.Diagnostics.CodeAnalysis;
6 +
7 + /// <summary>
8 + /// Contains specific information about an error encountered by the <see cref="View.Validate"/>,
9 + /// <see cref="View.ValidateNew"/>, or <see cref="View.ValidateFields"/> methods of the
10 + /// <see cref="View"/> class.
11 + /// </summary>
12 + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
13 + public struct ValidationErrorInfo
14 + {
15 + private ValidationError error;
16 + private string column;
17 +
18 + internal ValidationErrorInfo(ValidationError error, string column)
19 + {
20 + this.error = error;
21 + this.column = column;
22 + }
23 +
24 + /// <summary>
25 + /// Gets the type of validation error encountered.
26 + /// </summary>
27 + public ValidationError Error
28 + {
29 + get
30 + {
31 + return this.error;
32 + }
33 + }
34 +
35 + /// <summary>
36 + /// Gets the column containing the error, or null if the error applies to the whole row.
37 + /// </summary>
38 + public string Column
39 + {
40 + get
41 + {
42 + return this.column;
43 + }
44 + }
45 + }
46 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/View.cs new
+625
@@ -0,0 +1,625 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.Text;
7 + using System.Collections.Generic;
8 + using System.Globalization;
9 + using System.Diagnostics.CodeAnalysis;
10 +
11 + /// <summary>
12 + /// A View represents a result set obtained when processing a query using the
13 + /// <see cref="WixToolset.Dtf.WindowsInstaller.Database.OpenView"/> method of a
14 + /// <see cref="Database"/>. Before any data can be transferred,
15 + /// the query must be executed using the <see cref="Execute(Record)"/> method, passing to
16 + /// it all replaceable parameters designated within the SQL query string.
17 + /// </summary>
18 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
19 + public class View : InstallerHandle, IEnumerable<Record>
20 + {
21 + private Database database;
22 + private string sql;
23 + private IList<TableInfo> tables;
24 + private ColumnCollection columns;
25 +
26 + internal View(IntPtr handle, string sql, Database database)
27 + : base(handle, true)
28 + {
29 + this.sql = sql;
30 + this.database = database;
31 + }
32 +
33 + /// <summary>
34 + /// Gets the Database on which this View was opened.
35 + /// </summary>
36 + public Database Database
37 + {
38 + get { return this.database; }
39 + }
40 +
41 + /// <summary>
42 + /// Gets the SQL query string used to open this View.
43 + /// </summary>
44 + public string QueryString
45 + {
46 + get { return this.sql; }
47 + }
48 +
49 + /// <summary>
50 + /// Gets the set of tables that were included in the SQL query for this View.
51 + /// </summary>
52 + public IList<TableInfo> Tables
53 + {
54 + get
55 + {
56 + if (this.tables == null)
57 + {
58 + if (this.sql == null)
59 + {
60 + return null;
61 + }
62 +
63 + // Parse the table names out of the SQL query string by looking
64 + // for tokens that can come before or after the list of tables.
65 +
66 + string parseSql = this.sql.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ');
67 + string upperSql = parseSql.ToUpper(CultureInfo.InvariantCulture);
68 +
69 + string[] prefixes = new string[] { " FROM ", " INTO ", " TABLE " };
70 + string[] suffixes = new string[] { " WHERE ", " ORDER ", " SET ", " (", " ADD " };
71 +
72 + int index;
73 +
74 + for (int i = 0; i < prefixes.Length; i++)
75 + {
76 + if ((index = upperSql.IndexOf(prefixes[i], StringComparison.Ordinal)) > 0)
77 + {
78 + parseSql = parseSql.Substring(index + prefixes[i].Length);
79 + upperSql = upperSql.Substring(index + prefixes[i].Length);
80 + }
81 + }
82 +
83 + if (upperSql.StartsWith("UPDATE ", StringComparison.Ordinal))
84 + {
85 + parseSql = parseSql.Substring(7);
86 + upperSql = upperSql.Substring(7);
87 + }
88 +
89 + for (int i = 0; i < suffixes.Length; i++)
90 + {
91 + if ((index = upperSql.IndexOf(suffixes[i], StringComparison.Ordinal)) > 0)
92 + {
93 + parseSql = parseSql.Substring(0, index);
94 + upperSql = upperSql.Substring(0, index);
95 + }
96 + }
97 +
98 + if (upperSql.EndsWith(" HOLD", StringComparison.Ordinal) ||
99 + upperSql.EndsWith(" FREE", StringComparison.Ordinal))
100 + {
101 + parseSql = parseSql.Substring(0, parseSql.Length - 5);
102 + upperSql = upperSql.Substring(0, upperSql.Length - 5);
103 + }
104 +
105 + // At this point we should be left with a comma-separated list of table names,
106 + // optionally quoted with grave accent marks (`).
107 +
108 + string[] tableNames = parseSql.Split(',');
109 + IList<TableInfo> tableList = new List<TableInfo>(tableNames.Length);
110 + for (int i = 0; i < tableNames.Length; i++)
111 + {
112 + string tableName = tableNames[i].Trim(' ', '`');
113 + tableList.Add(new TableInfo(this.database, tableName));
114 + }
115 + this.tables = tableList;
116 + }
117 + return new List<TableInfo>(this.tables);
118 + }
119 + }
120 +
121 + /// <summary>
122 + /// Gets the set of columns that were included in the query for this View,
123 + /// or null if this view is not a SELECT query.
124 + /// </summary>
125 + /// <exception cref="InstallerException">the View is not in an active state</exception>
126 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
127 + /// <remarks><p>
128 + /// Win32 MSI API:
129 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgetcolumninfo.asp">MsiViewGetColumnInfo</a>
130 + /// </p></remarks>
131 + public ColumnCollection Columns
132 + {
133 + get
134 + {
135 + if (this.columns == null)
136 + {
137 + this.columns = new ColumnCollection(this);
138 + }
139 + return this.columns;
140 + }
141 + }
142 +
143 + /// <summary>
144 + /// Executes a SQL View query and supplies any required parameters. The query uses the
145 + /// question mark token to represent parameters as described in SQL Syntax. The values of
146 + /// these parameters are passed in as the corresponding fields of a parameter record.
147 + /// </summary>
148 + /// <param name="executeParams">Optional Record that supplies the parameters. This
149 + /// Record contains values to replace the parameter tokens in the SQL query.</param>
150 + /// <exception cref="InstallerException">the View could not be executed</exception>
151 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
152 + /// <remarks><p>
153 + /// Win32 MSI API:
154 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
155 + /// </p></remarks>
156 + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Params"), SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
157 + public void Execute(Record executeParams)
158 + {
159 + uint ret = RemotableNativeMethods.MsiViewExecute(
160 + (int) this.Handle,
161 + (executeParams != null ? (int) executeParams.Handle : 0));
162 + if (ret == (uint) NativeMethods.Error.BAD_QUERY_SYNTAX)
163 + {
164 + throw new BadQuerySyntaxException(this.sql);
165 + }
166 + else if (ret != 0)
167 + {
168 + throw InstallerException.ExceptionFromReturnCode(ret);
169 + }
170 + }
171 +
172 + /// <summary>
173 + /// Executes a SQL View query.
174 + /// </summary>
175 + /// <exception cref="InstallerException">the View could not be executed</exception>
176 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
177 + /// <remarks><p>
178 + /// Win32 MSI API:
179 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewexecute.asp">MsiViewExecute</a>
180 + /// </p></remarks>
181 + public void Execute() { this.Execute(null); }
182 +
183 + /// <summary>
184 + /// Fetches the next sequential record from the view, or null if there are no more records.
185 + /// </summary>
186 + /// <exception cref="InstallerException">the View is not in an active state</exception>
187 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
188 + /// <remarks><p>
189 + /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
190 + /// It is best that the handle be closed manually as soon as it is no longer
191 + /// needed, as leaving lots of unused handles open can degrade performance.
192 + /// </p><p>
193 + /// Win32 MSI API:
194 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
195 + /// </p></remarks>
196 + public Record Fetch()
197 + {
198 + int recordHandle;
199 + uint ret = RemotableNativeMethods.MsiViewFetch((int) this.Handle, out recordHandle);
200 + if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS)
201 + {
202 + return null;
203 + }
204 + else if (ret != 0)
205 + {
206 + throw InstallerException.ExceptionFromReturnCode(ret);
207 + }
208 +
209 + Record r = new Record((IntPtr) recordHandle, true, this);
210 + r.IsFormatStringInvalid = true;
211 + return r;
212 + }
213 +
214 + /// <summary>
215 + /// Updates a fetched Record.
216 + /// </summary>
217 + /// <param name="mode">specifies the modify mode</param>
218 + /// <param name="record">the Record to modify</param>
219 + /// <exception cref="InstallerException">the modification failed,
220 + /// or a validation was requested and the data did not pass</exception>
221 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
222 + /// <remarks><p>
223 + /// You can update or delete a record immediately after inserting, or seeking provided you
224 + /// have NOT modified the 0th field of the inserted or sought record.
225 + /// </p><p>
226 + /// To execute any SQL statement, a View must be created. However, a View that does not
227 + /// create a result set, such as CREATE TABLE, or INSERT INTO, cannot be used with any of
228 + /// the Modify methods to update tables though the view.
229 + /// </p><p>
230 + /// You cannot fetch a record containing binary data from one database and then use
231 + /// that record to insert the data into another database. To move binary data from one database
232 + /// to another, you should export the data to a file and then import it into the new database
233 + /// using a query and the <see cref="Record.SetStream(int,string)"/>. This ensures that each database has
234 + /// its own copy of the binary data.
235 + /// </p><p>
236 + /// Note that custom actions can only add, modify, or remove temporary rows, columns,
237 + /// or tables from a database. Custom actions cannot modify persistent data in a database,
238 + /// such as data that is a part of the database stored on disk.
239 + /// </p><p>
240 + /// Win32 MSI API:
241 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
242 + /// </p></remarks>
243 + /// <seealso cref="Refresh"/>
244 + /// <seealso cref="Insert"/>
245 + /// <seealso cref="Update"/>
246 + /// <seealso cref="Assign"/>
247 + /// <seealso cref="Replace"/>
248 + /// <seealso cref="Delete"/>
249 + /// <seealso cref="InsertTemporary"/>
250 + /// <seealso cref="Seek"/>
251 + /// <seealso cref="Merge"/>
252 + /// <seealso cref="Validate"/>
253 + /// <seealso cref="ValidateNew"/>
254 + /// <seealso cref="ValidateFields"/>
255 + /// <seealso cref="ValidateDelete"/>
256 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
257 + public void Modify(ViewModifyMode mode, Record record)
258 + {
259 + if (record == null)
260 + {
261 + throw new ArgumentNullException("record");
262 + }
263 +
264 + uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
265 + if (mode == ViewModifyMode.Insert || mode == ViewModifyMode.InsertTemporary)
266 + {
267 + record.IsFormatStringInvalid = true;
268 + }
269 + if (ret != 0)
270 + {
271 + throw InstallerException.ExceptionFromReturnCode(ret);
272 + }
273 + }
274 +
275 + /// <summary>
276 + /// Refreshes the data in a Record.
277 + /// </summary>
278 + /// <param name="record">the Record to be refreshed</param>
279 + /// <exception cref="InstallerException">the refresh failed</exception>
280 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
281 + /// <remarks><p>
282 + /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails with
283 + /// a deleted Record. Works only with read-write Records.
284 + /// </p><p>
285 + /// See <see cref="Modify"/> for more remarks.
286 + /// </p><p>
287 + /// Win32 MSI API:
288 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
289 + /// </p></remarks>
290 + public void Refresh(Record record) { this.Modify(ViewModifyMode.Refresh, record); }
291 +
292 + /// <summary>
293 + /// Inserts a Record into the view.
294 + /// </summary>
295 + /// <param name="record">the Record to be inserted</param>
296 + /// <exception cref="InstallerException">the insertion failed</exception>
297 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
298 + /// <remarks><p>
299 + /// Fails if a row with the same primary keys exists. Fails with a read-only database.
300 + /// This method cannot be used with a View containing joins.
301 + /// </p><p>
302 + /// See <see cref="Modify"/> for more remarks.
303 + /// </p><p>
304 + /// Win32 MSI API:
305 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
306 + /// </p></remarks>
307 + public void Insert(Record record) { this.Modify(ViewModifyMode.Insert, record); }
308 +
309 + /// <summary>
310 + /// Updates the View with new data from the Record.
311 + /// </summary>
312 + /// <param name="record">the new data</param>
313 + /// <exception cref="InstallerException">the update failed</exception>
314 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
315 + /// <remarks><p>
316 + /// Only non-primary keys can be updated. The Record must have been obtained by calling
317 + /// <see cref="Fetch"/>. Fails with a deleted Record. Works only with read-write Records.
318 + /// </p><p>
319 + /// See <see cref="Modify"/> for more remarks.
320 + /// </p><p>
321 + /// Win32 MSI API:
322 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
323 + /// </p></remarks>
324 + public void Update(Record record) { this.Modify(ViewModifyMode.Update, record); }
325 +
326 + /// <summary>
327 + /// Updates or inserts a Record into the View.
328 + /// </summary>
329 + /// <param name="record">the Record to be assigned</param>
330 + /// <exception cref="InstallerException">the assignment failed</exception>
331 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
332 + /// <remarks><p>
333 + /// Updates record if the primary keys match an existing row and inserts if they do not match.
334 + /// Fails with a read-only database. This method cannot be used with a View containing joins.
335 + /// </p><p>
336 + /// See <see cref="Modify"/> for more remarks.
337 + /// </p><p>
338 + /// Win32 MSI API:
339 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
340 + /// </p></remarks>
341 + public void Assign(Record record) { this.Modify(ViewModifyMode.Assign, record); }
342 +
343 + /// <summary>
344 + /// Updates or deletes and inserts a Record into the View.
345 + /// </summary>
346 + /// <param name="record">the Record to be replaced</param>
347 + /// <exception cref="InstallerException">the replacement failed</exception>
348 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
349 + /// <remarks><p>
350 + /// The Record must have been obtained by calling <see cref="Fetch"/>. Updates record if the
351 + /// primary keys are unchanged. Deletes old row and inserts new if primary keys have changed.
352 + /// Fails with a read-only database. This method cannot be used with a View containing joins.
353 + /// </p><p>
354 + /// See <see cref="Modify"/> for more remarks.
355 + /// </p><p>
356 + /// Win32 MSI API:
357 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
358 + /// </p></remarks>
359 + public void Replace(Record record) { this.Modify(ViewModifyMode.Replace, record); }
360 +
361 + /// <summary>
362 + /// Deletes a Record from the View.
363 + /// </summary>
364 + /// <param name="record">the Record to be deleted</param>
365 + /// <exception cref="InstallerException">the deletion failed</exception>
366 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
367 + /// <remarks><p>
368 + /// The Record must have been obtained by calling <see cref="Fetch"/>. Fails if the row has been
369 + /// deleted. Works only with read-write records. This method cannot be used with a View containing joins.
370 + /// </p><p>
371 + /// See <see cref="Modify"/> for more remarks.
372 + /// </p><p>
373 + /// Win32 MSI API:
374 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
375 + /// </p></remarks>
376 + public void Delete(Record record) { this.Modify(ViewModifyMode.Delete, record); }
377 +
378 + /// <summary>
379 + /// Inserts a Record into the View. The inserted data is not persistent.
380 + /// </summary>
381 + /// <param name="record">the Record to be inserted</param>
382 + /// <exception cref="InstallerException">the insertion failed</exception>
383 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
384 + /// <remarks><p>
385 + /// Fails if a row with the same primary key exists. Works only with read-write records.
386 + /// This method cannot be used with a View containing joins.
387 + /// </p><p>
388 + /// See <see cref="Modify"/> for more remarks.
389 + /// </p><p>
390 + /// Win32 MSI API:
391 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
392 + /// </p></remarks>
393 + public void InsertTemporary(Record record) { this.Modify(ViewModifyMode.InsertTemporary, record); }
394 +
395 + /// <summary>
396 + /// Refreshes the information in the supplied record without changing the position
397 + /// in the result set and without affecting subsequent fetch operations.
398 + /// </summary>
399 + /// <param name="record">the Record to be filled with the result of the seek</param>
400 + /// <exception cref="InstallerException">the seek failed</exception>
401 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
402 + /// <remarks><p>
403 + /// After seeking, the Record may then be used for subsequent Update, Delete, and Refresh
404 + /// operations. All primary key columns of the table must be in the query and the Record must
405 + /// have at least as many fields as the query. Seek cannot be used with multi-table queries.
406 + /// This method cannot be used with a View containing joins.
407 + /// </p><p>
408 + /// See <see cref="Modify"/> for more remarks.
409 + /// </p><p>
410 + /// Win32 MSI API:
411 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
412 + /// </p></remarks>
413 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
414 + public bool Seek(Record record)
415 + {
416 + if (record == null)
417 + {
418 + throw new ArgumentNullException("record");
419 + }
420 +
421 + uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Seek, (int) record.Handle);
422 + record.IsFormatStringInvalid = true;
423 + if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
424 + {
425 + return false;
426 + }
427 + else if (ret != 0)
428 + {
429 + throw InstallerException.ExceptionFromReturnCode(ret);
430 + }
431 +
432 + return true;
433 + }
434 +
435 + /// <summary>
436 + /// Inserts or validates a record.
437 + /// </summary>
438 + /// <param name="record">the Record to be merged</param>
439 + /// <returns>true if the record was inserted or validated, false if there is an existing
440 + /// record with the same primary keys that is not identical</returns>
441 + /// <exception cref="InstallerException">the merge failed (for a reason other than invalid data)</exception>
442 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
443 + /// <remarks><p>
444 + /// Works only with read-write records. This method cannot be used with a
445 + /// View containing joins.
446 + /// </p><p>
447 + /// See <see cref="Modify"/> for more remarks.
448 + /// </p><p>
449 + /// Win32 MSI API:
450 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>
451 + /// </p></remarks>
452 + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
453 + public bool Merge(Record record)
454 + {
455 + if (record == null)
456 + {
457 + throw new ArgumentNullException("record");
458 + }
459 +
460 + uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) ViewModifyMode.Merge, (int) record.Handle);
461 + if (ret == (uint) NativeMethods.Error.FUNCTION_FAILED)
462 + {
463 + return false;
464 + }
465 + else if (ret != 0)
466 + {
467 + throw InstallerException.ExceptionFromReturnCode(ret);
468 + }
469 + return true;
470 + }
471 +
472 + /// <summary>
473 + /// Validates a record, returning information about any errors.
474 + /// </summary>
475 + /// <param name="record">the Record to be validated</param>
476 + /// <returns>null if the record was validated; if there is an existing record with
477 + /// the same primary keys that has conflicting data then error information is returned</returns>
478 + /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
479 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
480 + /// <remarks><p>
481 + /// The Record must have been obtained by calling <see cref="Fetch"/>.
482 + /// Works with read-write and read-only records. This method cannot be used
483 + /// with a View containing joins.
484 + /// </p><p>
485 + /// See <see cref="Modify"/> for more remarks.
486 + /// </p><p>
487 + /// Win32 MSI APIs:
488 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
489 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
490 + /// </p></remarks>
491 + public ICollection<ValidationErrorInfo> Validate(Record record) { return this.InternalValidate(ViewModifyMode.Validate, record); }
492 +
493 + /// <summary>
494 + /// Validates a new record, returning information about any errors.
495 + /// </summary>
496 + /// <param name="record">the Record to be validated</param>
497 + /// <returns>null if the record was validated; if there is an existing
498 + /// record with the same primary keys then error information is returned</returns>
499 + /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
500 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
501 + /// <remarks><p>
502 + /// Checks for duplicate keys. The Record must have been obtained by
503 + /// calling <see cref="Fetch"/>. Works with read-write and read-only records.
504 + /// This method cannot be used with a View containing joins.
505 + /// </p><p>
506 + /// See <see cref="Modify"/> for more remarks.
507 + /// </p><p>
508 + /// Win32 MSI APIs:
509 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
510 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
511 + /// </p></remarks>
512 + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
513 + public ICollection<ValidationErrorInfo> ValidateNew(Record record) { return this.InternalValidate(ViewModifyMode.ValidateNew, record); }
514 +
515 + /// <summary>
516 + /// Validates fields of a fetched or new record, returning information about any errors.
517 + /// Can validate one or more fields of an incomplete record.
518 + /// </summary>
519 + /// <param name="record">the Record to be validated</param>
520 + /// <returns>null if the record was validated; if there is an existing record with
521 + /// the same primary keys that has conflicting data then error information is returned</returns>
522 + /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
523 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
524 + /// <remarks><p>
525 + /// Works with read-write and read-only records. This method cannot be used with
526 + /// a View containing joins.
527 + /// </p><p>
528 + /// See <see cref="Modify"/> for more remarks.
529 + /// </p><p>
530 + /// Win32 MSI APIs:
531 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
532 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
533 + /// </p></remarks>
534 + public ICollection<ValidationErrorInfo> ValidateFields(Record record) { return this.InternalValidate(ViewModifyMode.ValidateField, record); }
535 +
536 + /// <summary>
537 + /// Validates a record that will be deleted later, returning information about any errors.
538 + /// </summary>
539 + /// <param name="record">the Record to be validated</param>
540 + /// <returns>null if the record is safe to delete; if another row refers to
541 + /// the primary keys of this row then error information is returned</returns>
542 + /// <exception cref="InstallerException">the validation failed (for a reason other than invalid data)</exception>
543 + /// <exception cref="InvalidHandleException">the View handle is invalid</exception>
544 + /// <remarks><p>
545 + /// Validation does not check for the existence of the primary keys of this row in properties
546 + /// or strings. Does not check if a column is a foreign key to multiple tables. Works with
547 + /// read-write and read-only records. This method cannot be used with a View containing joins.
548 + /// </p><p>
549 + /// See <see cref="Modify"/> for more remarks.
550 + /// </p><p>
551 + /// Win32 MSI APIs:
552 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewmodify.asp">MsiViewModify</a>,
553 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewgeterror.asp">MsiViewGetError</a>
554 + /// </p></remarks>
555 + public ICollection<ValidationErrorInfo> ValidateDelete(Record record) { return this.InternalValidate(ViewModifyMode.ValidateDelete, record); }
556 +
557 + /// <summary>
558 + /// Enumerates over the Records retrieved by the View.
559 + /// </summary>
560 + /// <returns>An enumerator of Record objects.</returns>
561 + /// <exception cref="InstallerException">The View was not <see cref="Execute(Record)"/>d before attempting the enumeration.</exception>
562 + /// <remarks><p>
563 + /// Each Record object should be <see cref="InstallerHandle.Close"/>d after use.
564 + /// It is best that the handle be closed manually as soon as it is no longer
565 + /// needed, as leaving lots of unused handles open can degrade performance.
566 + /// However, note that it is not necessary to complete the enumeration just
567 + /// for the purpose of closing handles, because Records are fetched lazily
568 + /// on each step of the enumeration.
569 + /// </p><p>
570 + /// Win32 MSI API:
571 + /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiviewfetch.asp">MsiViewFetch</a>
572 + /// </p></remarks>
573 + public IEnumerator<Record> GetEnumerator()
574 + {
575 + Record rec;
576 + while ((rec = this.Fetch()) != null)
577 + {
578 + yield return rec;
579 + }
580 + }
581 +
582 + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
583 + {
584 + return this.GetEnumerator();
585 + }
586 +
587 + private ICollection<ValidationErrorInfo> InternalValidate(ViewModifyMode mode, Record record)
588 + {
589 + uint ret = RemotableNativeMethods.MsiViewModify((int) this.Handle, (int) mode, (int) record.Handle);
590 + if (ret == (uint) NativeMethods.Error.INVALID_DATA)
591 + {
592 + ICollection<ValidationErrorInfo> errorInfo = new List<ValidationErrorInfo>();
593 + while (true)
594 + {
595 + uint bufSize = 40;
596 + StringBuilder column = new StringBuilder("", (int) bufSize);
597 + int error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
598 + if (error == -2 /*MSIDBERROR_MOREDATA*/)
599 + {
600 + column.Capacity = (int) ++bufSize;
601 + error = RemotableNativeMethods.MsiViewGetError((int) this.Handle, column, ref bufSize);
602 + }
603 +
604 + if (error == -3 /*MSIDBERROR_INVALIDARG*/)
605 + {
606 + throw InstallerException.ExceptionFromReturnCode((uint) NativeMethods.Error.INVALID_PARAMETER);
607 + }
608 + else if (error == 0 /*MSIDBERROR_NOERROR*/)
609 + {
610 + break;
611 + }
612 +
613 + errorInfo.Add(new ValidationErrorInfo((ValidationError) error, column.ToString()));
614 + }
615 +
616 + return errorInfo;
617 + }
618 + else if (ret != 0)
619 + {
620 + throw InstallerException.ExceptionFromReturnCode(ret);
621 + }
622 + return null;
623 + }
624 + }
625 +}
src/dtf/WixToolset.Dtf.WindowsInstaller/WindowsInstaller.cd new
+943
@@ -0,0 +1,943 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<ClassDiagram MajorVersion="1" MinorVersion="1">
3 + <Comment CommentText="Database classes">
4 + <Position X="3.433" Y="7.717" Height="0.26" Width="1.159" />
5 + </Comment>
6 + <Comment CommentText="Custom action classes">
7 + <Position X="5.791" Y="1.774" Height="0.312" Width="1.465" />
8 + </Comment>
9 + <Comment CommentText="Inventory classes">
10 + <Position X="12.323" Y="2.386" Height="0.292" Width="1.183" />
11 + </Comment>
12 + <Comment CommentText="Exceptions">
13 + <Position X="11.75" Y="9.25" Height="0.285" Width="0.798" />
14 + </Comment>
15 + <Comment CommentText="Callback delegates">
16 + <Position X="17.25" Y="8" Height="0.281" Width="1.25" />
17 + </Comment>
18 + <Comment CommentText="API enums">
19 + <Position X="21.992" Y="0.5" Height="0.283" Width="2.008" />
20 + </Comment>
21 + <Comment CommentText="Database column enums">
22 + <Position X="24.25" Y="0.5" Height="0.26" Width="1.983" />
23 + </Comment>
24 + <Class Name="WixToolset.Dtf.WindowsInstaller.Database">
25 + <Position X="0.5" Y="5" Width="2.25" />
26 + <Members>
27 + <Method Name="Database" Hidden="true" />
28 + <Field Name="deleteOnClose" Hidden="true" />
29 + <Method Name="Dispose" Hidden="true" />
30 + <Method Name="ExecutePropertyQuery" Hidden="true" />
31 + <Field Name="filePath" Hidden="true" />
32 + <Method Name="Open" Hidden="true" />
33 + <Field Name="openMode" Hidden="true" />
34 + <Field Name="summaryInfo" Hidden="true" />
35 + <Field Name="tables" Hidden="true" />
36 + </Members>
37 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
38 + <Path>
39 + <Point X="3.312" Y="2.119" />
40 + <Point X="3.312" Y="2.689" />
41 + <Point X="2.699" Y="2.689" />
42 + <Point X="2.699" Y="5" />
43 + </Path>
44 + </InheritanceLine>
45 + <AssociationLine Name="Tables" Type="WixToolset.Dtf.WindowsInstaller.TableInfo" FixedFromPoint="true" FixedToPoint="true">
46 + <Path>
47 + <Point X="2.75" Y="8.125" />
48 + <Point X="3.75" Y="8.125" />
49 + <Point X="3.75" Y="8.5" />
50 + </Path>
51 + <MemberNameLabel ManuallyPlaced="true">
52 + <Position X="0.293" Y="0.152" />
53 + </MemberNameLabel>
54 + </AssociationLine>
55 + <AssociationLine Name="SummaryInfo" Type="WixToolset.Dtf.WindowsInstaller.SummaryInfo" FixedFromPoint="true">
56 + <Path>
57 + <Point X="1.318" Y="5" />
58 + <Point X="1.318" Y="4.535" />
59 + </Path>
60 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
61 + <Position X="-1.155" Y="0.279" Height="0.16" Width="1.095" />
62 + </MemberNameLabel>
63 + </AssociationLine>
64 + <TypeIdentifier>
65 + <HashCode>QABACUgAACAAIAVUIAgQREACAAIAk0ABAIhmoEAAAAA=</HashCode>
66 + <FileName>Database.cs</FileName>
67 + </TypeIdentifier>
68 + <ShowAsAssociation>
69 + <Property Name="SummaryInfo" />
70 + </ShowAsAssociation>
71 + <ShowAsCollectionAssociation>
72 + <Property Name="Tables" />
73 + </ShowAsCollectionAssociation>
74 + </Class>
75 + <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerHandle">
76 + <Position X="3" Y="0.5" Width="2" />
77 + <Members>
78 + <Method Name="Dispose" Hidden="true" />
79 + <Method Name="Equals" Hidden="true" />
80 + <Method Name="GetHashCode" Hidden="true" />
81 + <Field Name="handle" Hidden="true" />
82 + <Method Name="InstallerHandle" Hidden="true" />
83 + <Property Name="Sync" Hidden="true" />
84 + </Members>
85 + <TypeIdentifier>
86 + <HashCode>AAAAAAAAACAAAACAgAAAAAAAAAAAAMIAAAACAACAAAA=</HashCode>
87 + <FileName>Handle.cs</FileName>
88 + </TypeIdentifier>
89 + <Lollipop Position="0.2" />
90 + </Class>
91 + <Class Name="WixToolset.Dtf.WindowsInstaller.Installer">
92 + <Position X="19.25" Y="0.5" Width="2.5" />
93 + <Members>
94 + <Method Name="CheckInstallResult" Hidden="true" />
95 + <Field Name="errorResources" Hidden="true" />
96 + <Property Name="ErrorResources" Hidden="true" />
97 + <Field Name="externalUIHandlers" Hidden="true" />
98 + <Method Name="GetMessageFromModule" Hidden="true" />
99 + <Method Name="GetPatchStringDataType" Hidden="true" />
100 + <Field Name="rebootInitiated" Hidden="true" />
101 + <Field Name="rebootRequired" Hidden="true" />
102 + </Members>
103 + <TypeIdentifier>
104 + <HashCode>IAQBiIgAgAKAAAAELEAAPBgJAUAAYAAAAOQDagAgAQE=</HashCode>
105 + <FileName>ExternalUIHandler.cs</FileName>
106 + </TypeIdentifier>
107 + </Class>
108 + <Class Name="WixToolset.Dtf.WindowsInstaller.Record">
109 + <Position X="5.25" Y="3" Width="2" />
110 + <Members>
111 + <Method Name="CheckRange" Hidden="true" />
112 + <Method Name="ExtractSubStorage" Hidden="true" />
113 + <Method Name="FindColumn" Hidden="true" />
114 + <Method Name="Record" Hidden="true" />
115 + <Field Name="view" Hidden="true" />
116 + </Members>
117 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
118 + <Path>
119 + <Point X="3.688" Y="2.119" />
120 + <Point X="3.688" Y="2.672" />
121 + <Point X="5.4" Y="2.672" />
122 + <Point X="5.4" Y="3" />
123 + </Path>
124 + </InheritanceLine>
125 + <TypeIdentifier>
126 + <HashCode>AIAAAAAAAAAAAIAEiEIAJAAAAQgIEIAIIQCAAABIAAA=</HashCode>
127 + <FileName>Record.cs</FileName>
128 + </TypeIdentifier>
129 + </Class>
130 + <Class Name="WixToolset.Dtf.WindowsInstaller.Session">
131 + <Position X="7.5" Y="0.5" Width="2.25" />
132 + <Members>
133 + <Field Name="database" Hidden="true" />
134 + <Method Name="Dispose" Hidden="true" />
135 + <Method Name="IFormatProvider.GetFormat" Hidden="true" />
136 + <Method Name="Session" Hidden="true" />
137 + </Members>
138 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true">
139 + <Path>
140 + <Point X="3.875" Y="2.119" />
141 + <Point X="3.875" Y="2.395" />
142 + <Point X="7.5" Y="2.395" />
143 + </Path>
144 + </InheritanceLine>
145 + <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true">
146 + <Path>
147 + <Point X="7.562" Y="4.696" />
148 + <Point X="7.562" Y="10.375" />
149 + <Point X="2.75" Y="10.375" />
150 + </Path>
151 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
152 + <Position X="4.869" Y="5.46" Height="0.16" Width="0.806" />
153 + </MemberNameLabel>
154 + </AssociationLine>
155 + <AssociationLine Name="Components" Type="WixToolset.Dtf.WindowsInstaller.ComponentInfo" FixedFromPoint="true">
156 + <Path>
157 + <Point X="8.562" Y="4.696" />
158 + <Point X="8.562" Y="5.5" />
159 + </Path>
160 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
161 + <Position X="-1.021" Y="0.317" Height="0.16" Width="0.986" />
162 + </MemberNameLabel>
163 + </AssociationLine>
164 + <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInfo">
165 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
166 + <Position X="0.016" Y="2.568" Height="0.16" Width="0.785" />
167 + </MemberNameLabel>
168 + </AssociationLine>
169 + <TypeIdentifier>
170 + <HashCode>AAAAEAAAKGAAAoDADIBBAAAAAAgIEABBEIAAIAAACAA=</HashCode>
171 + <FileName>Session.cs</FileName>
172 + </TypeIdentifier>
173 + <ShowAsAssociation>
174 + <Property Name="Database" />
175 + </ShowAsAssociation>
176 + <ShowAsCollectionAssociation>
177 + <Property Name="Components" />
178 + <Property Name="Features" />
179 + </ShowAsCollectionAssociation>
180 + <Lollipop Position="0.2" />
181 + </Class>
182 + <Class Name="WixToolset.Dtf.WindowsInstaller.SummaryInfo">
183 + <Position X="0.5" Y="0.5" Width="2" />
184 + <Members>
185 + <Field Name="MAX_PROPERTIES" Hidden="true" />
186 + <Method Name="OpenSummaryInfo" Hidden="true" />
187 + <Method Name="SummaryInfo" Hidden="true" />
188 + <Property Name="this" Hidden="true" />
189 + </Members>
190 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true">
191 + <Path>
192 + <Point X="3.125" Y="2.119" />
193 + <Point X="3.125" Y="2.404" />
194 + <Point X="2.5" Y="2.404" />
195 + </Path>
196 + </InheritanceLine>
197 + <TypeIdentifier>
198 + <HashCode>AQAAAAAAAEAAAEAAjMgAAJIAAAEAAAAAAAAEhAAAoCI=</HashCode>
199 + <FileName>SummaryInfo.cs</FileName>
200 + </TypeIdentifier>
201 + </Class>
202 + <Class Name="WixToolset.Dtf.WindowsInstaller.View">
203 + <Position X="3" Y="3" Width="2" />
204 + <Members>
205 + <Field Name="columns" Hidden="true" />
206 + <Field Name="database" Hidden="true" />
207 + <Method Name="InternalValidate" Hidden="true" />
208 + <Field Name="sql" Hidden="true" />
209 + <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" />
210 + <Field Name="tables" Hidden="true" />
211 + <Method Name="View" Hidden="true" />
212 + </Members>
213 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerHandle" FixedFromPoint="true" FixedToPoint="true">
214 + <Path>
215 + <Point X="3.5" Y="2.119" />
216 + <Point X="3.5" Y="3" />
217 + </Path>
218 + </InheritanceLine>
219 + <AssociationLine Name="Database" Type="WixToolset.Dtf.WindowsInstaller.Database" FixedFromPoint="true" FixedToPoint="true">
220 + <Path>
221 + <Point X="3.75" Y="7.196" />
222 + <Point X="3.75" Y="7.562" />
223 + <Point X="2.75" Y="7.562" />
224 + </Path>
225 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
226 + <Position X="0.168" Y="0.16" Height="0.16" Width="0.831" />
227 + </MemberNameLabel>
228 + </AssociationLine>
229 + <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true">
230 + <Path>
231 + <Point X="4.25" Y="7.196" />
232 + <Point X="4.25" Y="7.562" />
233 + <Point X="5.25" Y="7.562" />
234 + </Path>
235 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
236 + <Position X="0.168" Y="0.152" Height="0.16" Width="0.787" />
237 + </MemberNameLabel>
238 + </AssociationLine>
239 + <TypeIdentifier>
240 + <HashCode>ICQAIEQAAEDgABRACQEAQECBAAEAAQAACAAAAAgAACA=</HashCode>
241 + <FileName>View.cs</FileName>
242 + </TypeIdentifier>
243 + <ShowAsAssociation>
244 + <Property Name="Database" />
245 + </ShowAsAssociation>
246 + <ShowAsCollectionAssociation>
247 + <Property Name="Columns" />
248 + </ShowAsCollectionAssociation>
249 + <Lollipop Position="0.638" />
250 + </Class>
251 + <Class Name="WixToolset.Dtf.WindowsInstaller.TableInfo">
252 + <Position X="3" Y="8.5" Width="2" />
253 + <Members>
254 + <Field Name="columns" Hidden="true" />
255 + <Method Name="GetTablePrimaryKeys" Hidden="true" />
256 + <Field Name="name" Hidden="true" />
257 + <Field Name="primaryKeys" Hidden="true" />
258 + <Method Name="TableInfo" Hidden="true" />
259 + <Method Name="ToString" Hidden="true" />
260 + </Members>
261 + <AssociationLine Name="Columns" Type="WixToolset.Dtf.WindowsInstaller.ColumnInfo" FixedFromPoint="true" FixedToPoint="true">
262 + <Path>
263 + <Point X="4.25" Y="8.5" />
264 + <Point X="4.25" Y="8.125" />
265 + <Point X="5.25" Y="8.125" />
266 + </Path>
267 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
268 + <Position X="0.143" Y="-0.338" Height="0.16" Width="0.787" />
269 + </MemberNameLabel>
270 + </AssociationLine>
271 + <TypeIdentifier>
272 + <HashCode>AAAAAAAAAEAAAAAEACAAAAQAQAAEAAAASAAAAAgAAAA=</HashCode>
273 + <FileName>TableInfo.cs</FileName>
274 + </TypeIdentifier>
275 + <ShowAsCollectionAssociation>
276 + <Property Name="Columns" />
277 + </ShowAsCollectionAssociation>
278 + </Class>
279 + <Class Name="WixToolset.Dtf.WindowsInstaller.ColumnInfo">
280 + <Position X="5.25" Y="7.25" Width="2" />
281 + <Members>
282 + <Method Name="ColumnInfo" Hidden="true" />
283 + <Field Name="isLocalizable" Hidden="true" />
284 + <Field Name="isRequired" Hidden="true" />
285 + <Field Name="isTemporary" Hidden="true" />
286 + <Field Name="name" Hidden="true" />
287 + <Field Name="size" Hidden="true" />
288 + <Method Name="ToString" Hidden="true" />
289 + <Field Name="type" Hidden="true" />
290 + </Members>
291 + <TypeIdentifier>
292 + <HashCode>AgAAAAAAAAAAgCAEEIAAABQgQAAFIAAAAQAAAAIAEAA=</HashCode>
293 + <FileName>ColumnInfo.cs</FileName>
294 + </TypeIdentifier>
295 + </Class>
296 + <Class Name="WixToolset.Dtf.WindowsInstaller.CustomActionAttribute">
297 + <Position X="5.25" Y="0.5" Width="2" />
298 + <Members>
299 + <Method Name="CustomActionAttribute" Hidden="true" />
300 + <Field Name="name" Hidden="true" />
301 + </Members>
302 + <TypeIdentifier>
303 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAA=</HashCode>
304 + <FileName>CustomActionAttribute.cs</FileName>
305 + </TypeIdentifier>
306 + </Class>
307 + <Class Name="WixToolset.Dtf.WindowsInstaller.Installation">
308 + <Position X="12.5" Y="0.5" Width="2" />
309 + <Members>
310 + <Field Name="context" Hidden="true" />
311 + <Method Name="Installation" Hidden="true" />
312 + <Field Name="installationCode" Hidden="true" />
313 + <Property Name="InstallationCode" Hidden="true" />
314 + <Property Name="InstallationType" Hidden="true" />
315 + <Field Name="sourceList" Hidden="true" />
316 + <Field Name="userSid" Hidden="true" />
317 + </Members>
318 + <AssociationLine Name="SourceList" Type="WixToolset.Dtf.WindowsInstaller.SourceList" FixedFromPoint="true" FixedToPoint="true">
319 + <Path>
320 + <Point X="13.75" Y="1.95" />
321 + <Point X="13.75" Y="2.298" />
322 + <Point X="14.75" Y="2.298" />
323 + </Path>
324 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
325 + <Position X="0.076" Y="0.114" Height="0.16" Width="0.868" />
326 + </MemberNameLabel>
327 + </AssociationLine>
328 + <TypeIdentifier>
329 + <HashCode>AAACIhAAAoAQAACACAAAAABAAAAAAAAAAAAEAAAAAAA=</HashCode>
330 + <FileName>Installation.cs</FileName>
331 + </TypeIdentifier>
332 + <ShowAsAssociation>
333 + <Property Name="SourceList" />
334 + </ShowAsAssociation>
335 + </Class>
336 + <Class Name="WixToolset.Dtf.WindowsInstaller.InstallationPart">
337 + <Position X="17" Y="3" Width="2" />
338 + <Members>
339 + <Field Name="id" Hidden="true" />
340 + <Property Name="Id" Hidden="true" />
341 + <Method Name="InstallationPart" Hidden="true" />
342 + <Field Name="productCode" Hidden="true" />
343 + <Property Name="ProductCode" Hidden="true" />
344 + </Members>
345 + <AssociationLine Name="Product" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
346 + <Path>
347 + <Point X="17.611" Y="3.967" />
348 + <Point X="17.611" Y="4.519" />
349 + <Point X="14.76" Y="4.519" />
350 + <Point X="14.76" Y="6.438" />
351 + <Point X="12.25" Y="6.438" />
352 + </Path>
353 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
354 + <Position X="4.65" Y="2.262" Height="0.16" Width="0.703" />
355 + </MemberNameLabel>
356 + </AssociationLine>
357 + <TypeIdentifier>
358 + <HashCode>AAAiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAIgA=</HashCode>
359 + <FileName>InstallationPart.cs</FileName>
360 + </TypeIdentifier>
361 + <ShowAsAssociation>
362 + <Property Name="Product" />
363 + </ShowAsAssociation>
364 + </Class>
365 + <Class Name="WixToolset.Dtf.WindowsInstaller.ProductInstallation">
366 + <Position X="10" Y="0.5" Width="2.25" />
367 + <Members>
368 + <Method Name="EnumFeatures" Hidden="true" />
369 + <Method Name="EnumProducts" Hidden="true" />
370 + <Method Name="EnumRelatedProducts" Hidden="true" />
371 + <Property Name="InstallationType" Hidden="true" />
372 + <Field Name="properties" Hidden="true" />
373 + <Property Name="State" Hidden="true" />
374 + </Members>
375 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true">
376 + <Path>
377 + <Point X="13.312" Y="1.95" />
378 + <Point X="13.312" Y="2.312" />
379 + <Point X="12.25" Y="2.312" />
380 + </Path>
381 + </InheritanceLine>
382 + <AssociationLine Name="AllProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true" FixedToPoint="true">
383 + <Path>
384 + <Point X="10.5" Y="7.273" />
385 + <Point X="10.5" Y="7.523" />
386 + <Point X="11.562" Y="7.523" />
387 + <Point X="11.562" Y="7.273" />
388 + </Path>
389 + <MemberNameLabel ManuallyPlaced="true">
390 + <Position X="0.068" Y="0.052" />
391 + </MemberNameLabel>
392 + </AssociationLine>
393 + <AssociationLine Name="Features" Type="WixToolset.Dtf.WindowsInstaller.FeatureInstallation" FixedToPoint="true">
394 + <Path>
395 + <Point X="12.25" Y="6.813" />
396 + <Point X="15" Y="6.813" />
397 + </Path>
398 + <MemberNameLabel ManuallyPlaced="true">
399 + <Position X="1.922" Y="0.05" />
400 + </MemberNameLabel>
401 + </AssociationLine>
402 + <TypeIdentifier>
403 + <HashCode>UAIEYrAGAAAACQBAKEABAAoASA0ARQAASUIGBAAIIAA=</HashCode>
404 + <FileName>ProductInstallation.cs</FileName>
405 + </TypeIdentifier>
406 + <ShowAsCollectionAssociation>
407 + <Property Name="AllProducts" />
408 + <Property Name="Features" />
409 + </ShowAsCollectionAssociation>
410 + </Class>
411 + <Class Name="WixToolset.Dtf.WindowsInstaller.PatchInstallation">
412 + <Position X="12.5" Y="2.75" Width="2" />
413 + <Members>
414 + <Method Name="EnumPatches" Hidden="true" />
415 + <Property Name="InstallationType" Hidden="true" />
416 + <Method Name="PatchInstallation" Hidden="true" />
417 + <Field Name="productCode" Hidden="true" />
418 + <Property Name="State" Hidden="true" />
419 + </Members>
420 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.Installation" FixedFromPoint="true" FixedToPoint="true">
421 + <Path>
422 + <Point X="13.562" Y="1.95" />
423 + <Point X="13.562" Y="2.75" />
424 + </Path>
425 + </InheritanceLine>
426 + <AssociationLine Name="AllPatches" Type="WixToolset.Dtf.WindowsInstaller.PatchInstallation" FixedFromPoint="true" FixedToPoint="true">
427 + <Path>
428 + <Point X="12.938" Y="5.98" />
429 + <Point X="12.938" Y="6.23" />
430 + <Point X="14" Y="6.23" />
431 + <Point X="14" Y="5.98" />
432 + </Path>
433 + <MemberNameLabel ManuallyPlaced="true">
434 + <Position X="0.093" Y="0.06" />
435 + </MemberNameLabel>
436 + </AssociationLine>
437 + <TypeIdentifier>
438 + <HashCode>AAAgsgCACAAAAAAAKEAAAgAAAgAAAAAAAgIAFAAAIAA=</HashCode>
439 + <FileName>PatchInstallation.cs</FileName>
440 + </TypeIdentifier>
441 + <ShowAsCollectionAssociation>
442 + <Property Name="AllPatches" />
443 + </ShowAsCollectionAssociation>
444 + </Class>
445 + <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation">
446 + <Position X="17" Y="5" Width="2" />
447 + <Members>
448 + <Method Name="ComponentInstallation" Hidden="true" />
449 + <Method Name="EnumClients" Hidden="true" />
450 + <Method Name="EnumComponents" Hidden="true" />
451 + <Method Name="EnumQualifiers" Hidden="true" />
452 + <Method Name="GetProductCode" Hidden="true" />
453 + </Members>
454 + <Compartments>
455 + <Compartment Name="Nested Types" Collapsed="false" />
456 + </Compartments>
457 + <NestedTypes>
458 + <Struct Name="WixToolset.Dtf.WindowsInstaller.ComponentInstallation.Qualifier" Collapsed="true">
459 + <TypeIdentifier>
460 + <NewMemberFileName>ComponentInstallation.cs</NewMemberFileName>
461 + </TypeIdentifier>
462 + </Struct>
463 + </NestedTypes>
464 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true">
465 + <Path>
466 + <Point X="18.111" Y="3.967" />
467 + <Point X="18.111" Y="5" />
468 + </Path>
469 + </InheritanceLine>
470 + <AssociationLine Name="AllComponents" Type="WixToolset.Dtf.WindowsInstaller.ComponentInstallation" FixedFromPoint="true" FixedToPoint="true">
471 + <Path>
472 + <Point X="17.375" Y="7.443" />
473 + <Point X="17.375" Y="7.693" />
474 + <Point X="18.625" Y="7.693" />
475 + <Point X="18.625" Y="7.443" />
476 + </Path>
477 + <MemberNameLabel ManuallyPlaced="true">
478 + <Position X="0.068" Y="0.052" />
479 + </MemberNameLabel>
480 + </AssociationLine>
481 + <AssociationLine Name="ClientProducts" Type="WixToolset.Dtf.WindowsInstaller.ProductInstallation" FixedFromPoint="true">
482 + <Path>
483 + <Point X="17" Y="7.188" />
484 + <Point X="12.25" Y="7.188" />
485 + </Path>
486 + <MemberNameLabel ManuallyPlaced="true" ManuallySized="true">
487 + <Position X="3.658" Y="-0.195" Height="0.16" Width="1.087" />
488 + </MemberNameLabel>
489 + </AssociationLine>
490 + <TypeIdentifier>
491 + <HashCode>AUAAAAACAAAAAAIAEAgQAAQAAAAAAAAEAAAABAAAAAA=</HashCode>
492 + <FileName>ComponentInstallation.cs</FileName>
493 + </TypeIdentifier>
494 + <ShowAsCollectionAssociation>
495 + <Property Name="AllComponents" />
496 + <Property Name="ClientProducts" />
497 + </ShowAsCollectionAssociation>
498 + </Class>
499 + <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation">
500 + <Position X="15" Y="4.75" Width="1.75" />
501 + <Members>
502 + <Method Name="FeatureInstallation" Hidden="true" />
503 + </Members>
504 + <Compartments>
505 + <Compartment Name="Nested Types" Collapsed="false" />
506 + </Compartments>
507 + <NestedTypes>
508 + <Struct Name="WixToolset.Dtf.WindowsInstaller.FeatureInstallation.UsageData" Collapsed="true">
509 + <TypeIdentifier>
510 + <NewMemberFileName>FeatureInstallation.cs</NewMemberFileName>
511 + </TypeIdentifier>
512 + </Struct>
513 + </NestedTypes>
514 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallationPart" FixedFromPoint="true" FixedToPoint="true">
515 + <Path>
516 + <Point X="17.889" Y="3.967" />
517 + <Point X="17.889" Y="4.804" />
518 + <Point X="16.75" Y="4.804" />
519 + </Path>
520 + </InheritanceLine>
521 + <TypeIdentifier>
522 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAEAAAAACAAAAAABAAAAAA=</HashCode>
523 + <FileName>FeatureInstallation.cs</FileName>
524 + </TypeIdentifier>
525 + </Class>
526 + <Class Name="WixToolset.Dtf.WindowsInstaller.SourceList">
527 + <Position X="14.75" Y="0.5" Width="2" />
528 + <Members>
529 + <Method Name="ClearSourceType" Hidden="true" />
530 + <Method Name="Contains" Hidden="true" />
531 + <Method Name="CopyTo" Hidden="true" />
532 + <Method Name="EnumSources" Hidden="true" />
533 + <Field Name="installation" Hidden="true" />
534 + <Field Name="mediaList" Hidden="true" />
535 + <Method Name="SourceList" Hidden="true" />
536 + <Method Name="System.Collections.IEnumerable.GetEnumerator" Hidden="true" />
537 + </Members>
538 + <AssociationLine Name="MediaList" Type="WixToolset.Dtf.WindowsInstaller.MediaDisk" FixedFromPoint="true" FixedToPoint="true">
539 + <Path>
540 + <Point X="16.75" Y="2.375" />
541 + <Point X="17.667" Y="2.375" />
542 + <Point X="17.667" Y="1.789" />
543 + </Path>
544 + <MemberNameLabel ManuallyPlaced="true">
545 + <Position X="0.029" Y="0.377" />
546 + </MemberNameLabel>
547 + </AssociationLine>
548 + <TypeIdentifier>
549 + <HashCode>BCIAAEAAEABAABgQCAAABIQAAFAQAAAAAAAABARwIgA=</HashCode>
550 + <FileName>SourceList.cs</FileName>
551 + </TypeIdentifier>
552 + <ShowAsCollectionAssociation>
553 + <Property Name="MediaList" />
554 + </ShowAsCollectionAssociation>
555 + <Lollipop Position="0.2" />
556 + </Class>
557 + <Class Name="WixToolset.Dtf.WindowsInstaller.ComponentInfo">
558 + <Position X="8" Y="5.5" Width="1.5" />
559 + <Members>
560 + <Method Name="ComponentInfo" Hidden="true" />
561 + <Field Name="name" Hidden="true" />
562 + <Field Name="session" Hidden="true" />
563 + </Members>
564 + <TypeIdentifier>
565 + <HashCode>AQAAAAIAAAAAAAAAAAEAACQAAAAEAAAAAAAAAAAAAAA=</HashCode>
566 + <FileName>ComponentInfo.cs</FileName>
567 + </TypeIdentifier>
568 + </Class>
569 + <Class Name="WixToolset.Dtf.WindowsInstaller.FeatureInfo">
570 + <Position X="8" Y="7.5" Width="1.75" />
571 + <Members>
572 + <Method Name="FeatureInfo" Hidden="true" />
573 + <Field Name="name" Hidden="true" />
574 + <Field Name="session" Hidden="true" />
575 + </Members>
576 + <TypeIdentifier>
577 + <HashCode>AQAAAAIAAAggAEAAAAEAACQQAAAEAAAAAAAAAAAAAAA=</HashCode>
578 + <FileName>FeatureInfo.cs</FileName>
579 + </TypeIdentifier>
580 + </Class>
581 + <Class Name="WixToolset.Dtf.WindowsInstaller.InstallerException">
582 + <Position X="11.75" Y="7.5" Width="2.25" />
583 + <Members>
584 + <Method Name="Combine" Hidden="true" />
585 + <Field Name="errorCode" Hidden="true" />
586 + <Field Name="errorData" Hidden="true" />
587 + <Method Name="ExceptionFromReturnCode" Hidden="true" />
588 + <Method Name="GetObjectData" Hidden="true" />
589 + <Method Name="GetSystemMessage" Hidden="true" />
590 + <Method Name="InstallerException" Hidden="true" />
591 + <Method Name="SaveErrorRecord" Hidden="true" />
592 + </Members>
593 + <TypeIdentifier>
594 + <HashCode>hAAAAAAAAAAAAAQAAAAAAAAgAAgIAAAIAAABIAAAQAA=</HashCode>
595 + <FileName>Exceptions.cs</FileName>
596 + </TypeIdentifier>
597 + </Class>
598 + <Class Name="WixToolset.Dtf.WindowsInstaller.BadQuerySyntaxException" Collapsed="true">
599 + <Position X="11.75" Y="9.75" Width="2.25" />
600 + <Members>
601 + <Method Name="BadQuerySyntaxException" Hidden="true" />
602 + </Members>
603 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true">
604 + <Path>
605 + <Point X="12.938" Y="9.119" />
606 + <Point X="12.938" Y="9.75" />
607 + </Path>
608 + </InheritanceLine>
609 + <TypeIdentifier>
610 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
611 + <FileName>Exceptions.cs</FileName>
612 + </TypeIdentifier>
613 + </Class>
614 + <Class Name="WixToolset.Dtf.WindowsInstaller.InstallCanceledException" Collapsed="true">
615 + <Position X="14.25" Y="9.75" Width="2.25" />
616 + <Members>
617 + <Method Name="InstallCanceledException" Hidden="true" />
618 + </Members>
619 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
620 + <Path>
621 + <Point X="13.375" Y="9.119" />
622 + <Point X="13.375" Y="9.631" />
623 + <Point X="14.25" Y="9.631" />
624 + <Point X="14.25" Y="9.813" />
625 + </Path>
626 + </InheritanceLine>
627 + <TypeIdentifier>
628 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
629 + <FileName>Exceptions.cs</FileName>
630 + </TypeIdentifier>
631 + </Class>
632 + <Class Name="WixToolset.Dtf.WindowsInstaller.InvalidHandleException" Collapsed="true">
633 + <Position X="14.25" Y="9" Width="2.25" />
634 + <Members>
635 + <Method Name="InvalidHandleException" Hidden="true" />
636 + </Members>
637 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true" FixedToPoint="true">
638 + <Path>
639 + <Point X="13.812" Y="9.119" />
640 + <Point X="13.812" Y="9.375" />
641 + <Point X="14.25" Y="9.375" />
642 + </Path>
643 + </InheritanceLine>
644 + <TypeIdentifier>
645 + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
646 + <FileName>Exceptions.cs</FileName>
647 + </TypeIdentifier>
648 + </Class>
649 + <Class Name="WixToolset.Dtf.WindowsInstaller.MergeException">
650 + <Position X="14.25" Y="7.5" Width="2.25" />
651 + <Members>
652 + <Field Name="conflictCounts" Hidden="true" />
653 + <Field Name="conflictTables" Hidden="true" />
654 + <Method Name="GetObjectData" Hidden="true" />
655 + <Method Name="MergeException" Hidden="true" />
656 + </Members>
657 + <InheritanceLine Type="WixToolset.Dtf.WindowsInstaller.InstallerException" FixedFromPoint="true">
658 + <Path>
659 + <Point X="14" Y="8.813" />
660 + <Point X="14.25" Y="8.813" />
661 + </Path>
662 + </InheritanceLine>
663 + <TypeIdentifier>
664 + <HashCode>AAAAAAAAAAAAAEAAEABAABAgAAAAAAAAAAAAIAAAAAA=</HashCode>
665 + <FileName>Exceptions.cs</FileName>
666 + </TypeIdentifier>
667 + </Class>
668 + <Struct Name="WixToolset.Dtf.WindowsInstaller.MediaDisk">
669 + <Position X="17" Y="0.5" Width="2" />
670 + <Members>
671 + <Field Name="diskId" Hidden="true" />
672 + <Field Name="diskPrompt" Hidden="true" />
673 + <Method Name="MediaDisk" Hidden="true" />
674 + <Field Name="volumeLabel" Hidden="true" />
675 + </Members>
676 + <TypeIdentifier>
677 + <HashCode>AgAAAAAAAAAAAAAAAgAAIAAAACAAAAAEAAAABAAAAAA=</HashCode>
678 + <FileName>MediaDisk.cs</FileName>
679 + </TypeIdentifier>
680 + </Struct>
681 + <Struct Name="WixToolset.Dtf.WindowsInstaller.InstallCost">
682 + <Position X="10" Y="8" Width="1.25" />
683 + <Members>
684 + <Field Name="cost" Hidden="true" />
685 + <Field Name="driveName" Hidden="true" />
686 + <Method Name="InstallCost" Hidden="true" />
687 + <Field Name="tempCost" Hidden="true" />
688 + </Members>
689 + <TypeIdentifier>
690 + <HashCode>AAAAAAAAAgAAAAIAAAAACBAAAAgQAAAAAAAAAAAAAAA=</HashCode>
691 + <FileName>InstallCost.cs</FileName>
692 + </TypeIdentifier>
693 + </Struct>
694 + <Struct Name="WixToolset.Dtf.WindowsInstaller.ShortcutTarget">
695 + <Position X="19.5" Y="7.75" Width="1.5" />
696 + <Members>
697 + <Field Name="componentCode" Hidden="true" />
698 + <Method Name="Equals" Hidden="true" />
699 + <Field Name="feature" Hidden="true" />
700 + <Method Name="GetHashCode" Hidden="true" />
701 + <Method Name="operator !=" Hidden="true" />
702 + <Method Name="operator ==" Hidden="true" />
703 + <Field Name="productCode" Hidden="true" />
704 + <Method Name="ShortcutTarget" Hidden="true" />
705 + </Members>
706 + <TypeIdentifier>
707 + <HashCode>AAAgAAACAAAAAAAAgAAAAAAAAAAAAIAIAAIACAAAIiA=</HashCode>
708 + <FileName>ShortcutTarget.cs</FileName>
709 + </TypeIdentifier>
710 + </Struct>
711 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ActionResult" Collapsed="true">
712 + <Position X="22" Y="1" Width="2" />
713 + <TypeIdentifier>
714 + <HashCode>AAAAAAAAAAAAAAQgAAAAAAAAAgAAAACAAAAAAAACAAA=</HashCode>
715 + <FileName>Enums.cs</FileName>
716 + </TypeIdentifier>
717 + </Enum>
718 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ControlAttributes" Collapsed="true">
719 + <Position X="24.25" Y="1" Width="2" />
720 + <TypeIdentifier>
721 + <HashCode>AADAAlQAAAEgAoNFAAgAACAgAQAAAooMAAAAgCkCAkA=</HashCode>
722 + <FileName>ColumnEnums.cs</FileName>
723 + </TypeIdentifier>
724 + </Enum>
725 + <Enum Name="WixToolset.Dtf.WindowsInstaller.CustomActionTypes" Collapsed="true">
726 + <Position X="24.25" Y="1.5" Width="2" />
727 + <TypeIdentifier>
728 + <HashCode>AABCCFAAAAAhJAAHAAAAAoAAAAAAAAABCAIEEQEAgAA=</HashCode>
729 + <FileName>ColumnEnums.cs</FileName>
730 + </TypeIdentifier>
731 + </Enum>
732 + <Enum Name="WixToolset.Dtf.WindowsInstaller.DatabaseOpenMode" Collapsed="true">
733 + <Position X="22" Y="1.5" Width="2" />
734 + <TypeIdentifier>
735 + <HashCode>AAAKAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAYAAAAAAA=</HashCode>
736 + <FileName>Enums.cs</FileName>
737 + </TypeIdentifier>
738 + </Enum>
739 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallLogModes" Collapsed="true">
740 + <Position X="22" Y="2" Width="2" />
741 + <TypeIdentifier>
742 + <HashCode>AAABgEBAAIAAAQBAAAAAABEAAQAAABSAgAAEAEWQAAA=</HashCode>
743 + <FileName>Enums.cs</FileName>
744 + </TypeIdentifier>
745 + </Enum>
746 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMessage" Collapsed="true">
747 + <Position X="22" Y="2.5" Width="2" />
748 + <TypeIdentifier>
749 + <HashCode>AAABgABAAAAAAQBAAAAAABEAAQAAABSAgAAEAECAAAA=</HashCode>
750 + <FileName>Enums.cs</FileName>
751 + </TypeIdentifier>
752 + </Enum>
753 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallMode" Collapsed="true">
754 + <Position X="22" Y="3" Width="2" />
755 + <TypeIdentifier>
756 + <HashCode>AAAABAAAQAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAA=</HashCode>
757 + <FileName>Enums.cs</FileName>
758 + </TypeIdentifier>
759 + </Enum>
760 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallRunMode" Collapsed="true">
761 + <Position X="22" Y="3.5" Width="2" />
762 + <TypeIdentifier>
763 + <HashCode>AAgAIEACAAQAAAQCAAAAAQAACEAABAQAgAECAAAAAgA=</HashCode>
764 + <FileName>Enums.cs</FileName>
765 + </TypeIdentifier>
766 + </Enum>
767 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallState" Collapsed="true">
768 + <Position X="22" Y="4" Width="2" />
769 + <TypeIdentifier>
770 + <HashCode>AAAAABAAAgAAAFACgACQJAAAAABQAAQAAAAAAAAAABA=</HashCode>
771 + <FileName>Enums.cs</FileName>
772 + </TypeIdentifier>
773 + </Enum>
774 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallType" Collapsed="true">
775 + <Position X="22" Y="4.5" Width="2" />
776 + <TypeIdentifier>
777 + <HashCode>AAAAAAAAAAAAAAAAIAAAIAAAAAAAAAAAAgAAAAAAAAA=</HashCode>
778 + <FileName>Enums.cs</FileName>
779 + </TypeIdentifier>
780 + </Enum>
781 + <Enum Name="WixToolset.Dtf.WindowsInstaller.InstallUIOptions" Collapsed="true">
782 + <Position X="22" Y="5" Width="2" />
783 + <TypeIdentifier>
784 + <HashCode>AAAAAAAIACAAAAAAQAAAIACEAAAAAAAAAgIAAAGAAAA=</HashCode>
785 + <FileName>Enums.cs</FileName>
786 + </TypeIdentifier>
787 + </Enum>
788 + <Enum Name="WixToolset.Dtf.WindowsInstaller.MessageResult" Collapsed="true">
789 + <Position X="22" Y="5.5" Width="2" />
790 + <TypeIdentifier>
791 + <HashCode>AAAAAAAASAAQASBAAAAAAAAAACAAAAAAAAABAAEAAAA=</HashCode>
792 + <FileName>Enums.cs</FileName>
793 + </TypeIdentifier>
794 + </Enum>
795 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ReinstallModes" Collapsed="true">
796 + <Position X="22" Y="6" Width="2" />
797 + <TypeIdentifier>
798 + <HashCode>IAAAAAAAAACAAAAAAAAQAAAQBAAAABAAAAEAAgAIABA=</HashCode>
799 + <FileName>Enums.cs</FileName>
800 + </TypeIdentifier>
801 + </Enum>
802 + <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformErrors" Collapsed="true">
803 + <Position X="22" Y="6.5" Width="2" />
804 + <TypeIdentifier>
805 + <HashCode>AAIEAAAAAAABAAAAAAAAAAQAAAAAAAACAAAgAAEAABA=</HashCode>
806 + <FileName>Enums.cs</FileName>
807 + </TypeIdentifier>
808 + </Enum>
809 + <Enum Name="WixToolset.Dtf.WindowsInstaller.TransformValidations" Collapsed="true">
810 + <Position X="22" Y="7" Width="2" />
811 + <TypeIdentifier>
812 + <HashCode>AAACAAAACAQIAAAAAAAAAAAAAEgAAAQAAIAQAQEAAAQ=</HashCode>
813 + <FileName>Enums.cs</FileName>
814 + </TypeIdentifier>
815 + </Enum>
816 + <Enum Name="WixToolset.Dtf.WindowsInstaller.UserContexts" Collapsed="true">
817 + <Position X="22" Y="7.5" Width="2" />
818 + <TypeIdentifier>
819 + <HashCode>AAAAAAgAAAAAAAAEAAAAAAIAAAAAAAAAAIAAQAEAAAA=</HashCode>
820 + <FileName>Enums.cs</FileName>
821 + </TypeIdentifier>
822 + </Enum>
823 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ValidationError" Collapsed="true">
824 + <Position X="22" Y="8" Width="2" />
825 + <TypeIdentifier>
826 + <HashCode>oCMBAAABEAMAAAAAAAAQAAIASigAABKBABgCgEEAAA4=</HashCode>
827 + <FileName>Enums.cs</FileName>
828 + </TypeIdentifier>
829 + </Enum>
830 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ViewModifyMode" Collapsed="true">
831 + <Position X="22" Y="8.5" Width="2" />
832 + <TypeIdentifier>
833 + <HashCode>IAQAAEQAAADgAAAACAAAAACAAAEAAQAAAAAAAAAAACA=</HashCode>
834 + <FileName>Enums.cs</FileName>
835 + </TypeIdentifier>
836 + </Enum>
837 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ComponentAttributes" Collapsed="true">
838 + <Position X="24.25" Y="2" Width="2" />
839 + <TypeIdentifier>
840 + <HashCode>AAABAAAAIAAAAAAAEAAACAAAIAAAgQCAAAAgAAEAAAA=</HashCode>
841 + <FileName>ColumnEnums.cs</FileName>
842 + </TypeIdentifier>
843 + </Enum>
844 + <Enum Name="WixToolset.Dtf.WindowsInstaller.DialogAttributes" Collapsed="true">
845 + <Position X="24.25" Y="2.5" Width="2" />
846 + <TypeIdentifier>
847 + <HashCode>AAAIAAQAAAAAAQIEAAAAACAAEAAAAAAIgAAggAAAAgA=</HashCode>
848 + <FileName>ColumnEnums.cs</FileName>
849 + </TypeIdentifier>
850 + </Enum>
851 + <Enum Name="WixToolset.Dtf.WindowsInstaller.FeatureAttributes" Collapsed="true">
852 + <Position X="24.25" Y="3" Width="2" />
853 + <TypeIdentifier>
854 + <HashCode>AAAAQEAAAEAAAAAAAAAAAAAAEAAAAAABABAAAAEAAAA=</HashCode>
855 + <FileName>ColumnEnums.cs</FileName>
856 + </TypeIdentifier>
857 + </Enum>
858 + <Enum Name="WixToolset.Dtf.WindowsInstaller.FileAttributes" Collapsed="true">
859 + <Position X="24.25" Y="3.5" Width="2" />
860 + <TypeIdentifier>
861 + <HashCode>AAAAAABAAIAIAAAAAAAAAgAAAAAEAAAAAAEIAAEEAAA=</HashCode>
862 + <FileName>ColumnEnums.cs</FileName>
863 + </TypeIdentifier>
864 + </Enum>
865 + <Enum Name="WixToolset.Dtf.WindowsInstaller.IniFileAction" Collapsed="true">
866 + <Position X="24.25" Y="4" Width="2" />
867 + <TypeIdentifier>
868 + <HashCode>AggAAAAAAAAAEAEAAAAAAAAAAAABAAAAAAAAAAAAAAA=</HashCode>
869 + <FileName>ColumnEnums.cs</FileName>
870 + </TypeIdentifier>
871 + </Enum>
872 + <Enum Name="WixToolset.Dtf.WindowsInstaller.LocatorTypes" Collapsed="true">
873 + <Position X="24.25" Y="4.5" Width="2" />
874 + <TypeIdentifier>
875 + <HashCode>ABAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAABAAAA=</HashCode>
876 + <FileName>ColumnEnums.cs</FileName>
877 + </TypeIdentifier>
878 + </Enum>
879 + <Enum Name="WixToolset.Dtf.WindowsInstaller.RegistryRoot" Collapsed="true">
880 + <Position X="24.25" Y="5" Width="2" />
881 + <TypeIdentifier>
882 + <HashCode>AACgACAAAIAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAA=</HashCode>
883 + <FileName>ColumnEnums.cs</FileName>
884 + </TypeIdentifier>
885 + </Enum>
886 + <Enum Name="WixToolset.Dtf.WindowsInstaller.RemoveFileModes" Collapsed="true">
887 + <Position X="24.25" Y="5.5" Width="2" />
888 + <TypeIdentifier>
889 + <HashCode>AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAACAAAEAAAA=</HashCode>
890 + <FileName>ColumnEnums.cs</FileName>
891 + </TypeIdentifier>
892 + </Enum>
893 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceAttributes" Collapsed="true">
894 + <Position X="24.25" Y="6" Width="2" />
895 + <TypeIdentifier>
896 + <HashCode>AQCAAAAAQAAAAAAAAACAAAgAAAAAAAAAAgAAABEAAAI=</HashCode>
897 + <FileName>ColumnEnums.cs</FileName>
898 + </TypeIdentifier>
899 + </Enum>
900 + <Enum Name="WixToolset.Dtf.WindowsInstaller.ServiceControlEvents" Collapsed="true">
901 + <Position X="24.25" Y="6.5" Width="2" />
902 + <TypeIdentifier>
903 + <HashCode>AAAAAAAAACAAAAAAAAAAEAAAAAEAAgAAJAAAAAEAAAA=</HashCode>
904 + <FileName>ColumnEnums.cs</FileName>
905 + </TypeIdentifier>
906 + </Enum>
907 + <Enum Name="WixToolset.Dtf.WindowsInstaller.TextStyles" Collapsed="true">
908 + <Position X="24.25" Y="7" Width="2" />
909 + <TypeIdentifier>
910 + <HashCode>AAAAAAAAAAAAACAAAIAAAAAAAAACAAAAAAAAAAAAAAE=</HashCode>
911 + <FileName>ColumnEnums.cs</FileName>
912 + </TypeIdentifier>
913 + </Enum>
914 + <Enum Name="WixToolset.Dtf.WindowsInstaller.UpgradeAttributes" Collapsed="true">
915 + <Position X="24.25" Y="7.5" Width="2" />
916 + <TypeIdentifier>
917 + <HashCode>AEAAAAAAAAAAAAAQAAAAAAAAAAgIAAAAABAAAAAAIAA=</HashCode>
918 + <FileName>ColumnEnums.cs</FileName>
919 + </TypeIdentifier>
920 + </Enum>
921 + <Delegate Name="WixToolset.Dtf.WindowsInstaller.InapplicablePatchHandler" Collapsed="true">
922 + <Position X="16.75" Y="9.75" Width="2.25" />
923 + <TypeIdentifier>
924 + <HashCode>AAAAAAAAAACAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAA=</HashCode>
925 + <FileName>Installer.cs</FileName>
926 + </TypeIdentifier>
927 + </Delegate>
928 + <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIHandler" Collapsed="true">
929 + <Position X="16.75" Y="8.5" Width="2.25" />
930 + <TypeIdentifier>
931 + <HashCode>AAAEAAAAAAAAABAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode>
932 + <FileName>ExternalUIHandler.cs</FileName>
933 + </TypeIdentifier>
934 + </Delegate>
935 + <Delegate Name="WixToolset.Dtf.WindowsInstaller.ExternalUIRecordHandler" Collapsed="true">
936 + <Position X="16.75" Y="9" Width="2.25" />
937 + <TypeIdentifier>
938 + <HashCode>AAAEAAAAAAAAAAAAAAAABAAAAACBAAAAAAAAAAAAAAA=</HashCode>
939 + <FileName>ExternalUIHandler.cs</FileName>
940 + </TypeIdentifier>
941 + </Delegate>
942 + <Font Name="Verdana" Size="8" />
943 +</ClassDiagram>
\ No newline at end of file
src/dtf/WixToolset.Dtf.WindowsInstaller/WixToolset.Dtf.WindowsInstaller.csproj new
+30
@@ -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 +<Project Sdk="Microsoft.NET.Sdk">
5 + <PropertyGroup>
6 + <RootNamespace>WixToolset.Dtf.WindowsInstaller</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.WindowsInstaller</AssemblyName>
8 + <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 + <Description>Managed libraries for Windows Installer</Description>
10 + <CreateDocumentationFile>true</CreateDocumentationFile>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <EmbeddedResource Include="Errors.resources" />
15 + </ItemGroup>
16 +
17 + <ItemGroup>
18 + <None Include="Errors.txt" />
19 + <None Include="WindowsInstaller.cd" />
20 + </ItemGroup>
21 +
22 + <ItemGroup>
23 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
25 + </ItemGroup>
26 +
27 + <ItemGroup Condition=" '$(TargetFramework)'=='net20' ">
28 + <Reference Include="System.Configuration" />
29 + </ItemGroup>
30 +</Project>
src/dtf/WixToolset.Dtf.WindowsInstaller/customactiondata.cs new
+469
@@ -0,0 +1,469 @@
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 +namespace WixToolset.Dtf.WindowsInstaller
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Xml;
8 + using System.Xml.Serialization;
9 + using System.Text;
10 + using System.Collections.Generic;
11 + using System.Globalization;
12 + using System.Diagnostics.CodeAnalysis;
13 +
14 + /// <summary>
15 + /// Contains a collection of key-value pairs suitable for passing between
16 + /// immediate and deferred/rollback/commit custom actions.
17 + /// </summary>
18 + /// <remarks>
19 + /// Call the <see cref="CustomActionData.ToString" /> method to get a string
20 + /// suitable for storing in a property and reconstructing the custom action data later.
21 + /// </remarks>
22 + /// <seealso cref="Session.CustomActionData"/>
23 + /// <seealso cref="Session.DoAction(string,CustomActionData)"/>
24 + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
25 + public sealed class CustomActionData : IDictionary<string, string>
26 + {
27 + /// <summary>
28 + /// "CustomActionData" literal property name.
29 + /// </summary>
30 + public const string PropertyName = "CustomActionData";
31 +
32 + private const char DataSeparator = ';';
33 + private const char KeyValueSeparator = '=';
34 +
35 + private IDictionary<string, string> data;
36 +
37 + /// <summary>
38 + /// Creates a new empty custom action data object.
39 + /// </summary>
40 + public CustomActionData() : this(null)
41 + {
42 + }
43 +
44 + /// <summary>
45 + /// Reconstructs a custom action data object from data that was previously
46 + /// persisted in a string.
47 + /// </summary>
48 + /// <param name="keyValueList">Previous output from <see cref="CustomActionData.ToString" />.</param>
49 + public CustomActionData(string keyValueList)
50 + {
51 + this.data = new Dictionary<string, string>();
52 +
53 + if (keyValueList != null)
54 + {
55 + this.Parse(keyValueList);
56 + }
57 + }
58 +
59 + /// <summary>
60 + /// Adds a key and value to the data collection.
61 + /// </summary>
62 + /// <param name="key">Case-sensitive data key.</param>
63 + /// <param name="value">Data value (may be null).</param>
64 + /// <exception cref="ArgumentException">the key does not consist solely of letters,
65 + /// numbers, and the period, underscore, and space characters.</exception>
66 + public void Add(string key, string value)
67 + {
68 + CustomActionData.ValidateKey(key);
69 + this.data.Add(key, value);
70 + }
71 +
72 + /// <summary>
73 + /// Adds a value to the data collection, using XML serialization to persist the object as a string.
74 + /// </summary>
75 + /// <param name="key">Case-sensitive data key.</param>
76 + /// <param name="value">Data value (may be null).</param>
77 + /// <exception cref="ArgumentException">the key does not consist solely of letters,
78 + /// numbers, and the period, underscore, and space characters.</exception>
79 + /// <exception cref="NotSupportedException">The value type does not support XML serialization.</exception>
80 + /// <exception cref="InvalidOperationException">The value could not be serialized.</exception>
81 + public void AddObject<T>(string key, T value)
82 + {
83 + if (value == null)
84 + {
85 + this.Add(key, null);
86 + }
87 + else if (typeof(T) == typeof(string) ||
88 + typeof(T) == typeof(CustomActionData)) // Serialize nested CustomActionData
89 + {
90 + this.Add(key, value.ToString());
91 + }
92 + else
93 + {
94 + string valueString = CustomActionData.Serialize<T>(value);
95 + this.Add(key, valueString);
96 + }
97 + }
98 +
99 + /// <summary>
100 + /// Gets a value from the data collection, using XML serialization to load the object from a string.
101 + /// </summary>
102 + /// <param name="key">Case-sensitive data key.</param>
103 + /// <exception cref="InvalidOperationException">The value could not be deserialized.</exception>
104 + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
105 + public T GetObject<T>(string key)
106 + {
107 + string value = this[key];
108 + if (value == null)
109 + {
110 + return default(T);
111 + }
112 + else if (typeof(T) == typeof(string))
113 + {
114 + // Funny casting because the compiler doesn't know T is string here.
115 + return (T) (object) value;
116 + }
117 + else if (typeof(T) == typeof(CustomActionData))
118 + {
119 + // Deserialize nested CustomActionData.
120 + return (T) (object) new CustomActionData(value);
121 + }
122 + else if (value.Length == 0)
123 + {
124 + return default(T);
125 + }
126 + else
127 + {
128 + return CustomActionData.Deserialize<T>(value);
129 + }
130 + }
131 +
132 + /// <summary>
133 + /// Determines whether the data contains an item with the specified key.
134 + /// </summary>
135 + /// <param name="key">Case-sensitive data key.</param>
136 + /// <returns>true if the data contains an item with the key; otherwise, false</returns>
137 + public bool ContainsKey(string key)
138 + {
139 + return this.data.ContainsKey(key);
140 + }
141 +
142 + /// <summary>
143 + /// Gets a collection object containing all the keys of the data.
144 + /// </summary>
145 + public ICollection<string> Keys
146 + {
147 + get
148 + {
149 + return this.data.Keys;
150 + }
151 + }
152 +
153 + /// <summary>
154 + /// Removes the item with the specified key from the data.
155 + /// </summary>
156 + /// <param name="key">Case-sensitive data key.</param>
157 + /// <returns>true if the item was successfully removed from the data;
158 + /// false if an item with the specified key was not found</returns>
159 + public bool Remove(string key)
160 + {
161 + return this.data.Remove(key);
162 + }
163 +
164 + /// <summary>
165 + /// Gets the value with the specified key.
166 + /// </summary>
167 + /// <param name="key">Case-sensitive data key.</param>
168 + /// <param name="value">Value associated with the specified key, or
169 + /// null if an item with the specified key was not found</param>
170 + /// <returns>true if the data contains an item with the specified key; otherwise, false.</returns>
171 + public bool TryGetValue(string key, out string value)
172 + {
173 + return this.data.TryGetValue(key, out value);
174 + }
175 +
176 + /// <summary>
177 + /// Gets a collection containing all the values of the data.
178 + /// </summary>
179 + public ICollection<string> Values
180 + {
181 + get
182 + {
183 + return this.data.Values;
184 + }
185 + }
186 +
187 + /// <summary>
188 + /// Gets or sets a data value with a specified key.
189 + /// </summary>
190 + /// <param name="key">Case-sensitive data key.</param>
191 + /// <exception cref="ArgumentException">the key does not consist solely of letters,
192 + /// numbers, and the period, underscore, and space characters.</exception>
193 + public string this[string key]
194 + {
195 + get
196 + {
197 + return this.data[key];
198 + }
199 + set
200 + {
201 + CustomActionData.ValidateKey(key);
202 + this.data[key] = value;
203 + }
204 + }
205 +
206 + /// <summary>
207 + /// Adds an item with key and value to the data collection.
208 + /// </summary>
209 + /// <param name="item">Case-sensitive data key, with a data value that may be null.</param>
210 + /// <exception cref="ArgumentException">the key does not consist solely of letters,
211 + /// numbers, and the period, underscore, and space characters.</exception>
212 + public void Add(KeyValuePair<string, string> item)
213 + {
214 + CustomActionData.ValidateKey(item.Key);
215 + this.data.Add(item);
216 + }
217 +
218 + /// <summary>
219 + /// Removes all items from the data.
220 + /// </summary>
221 + public void Clear()
222 + {
223 + if (this.data.Count > 0)
224 + {
225 + this.data.Clear();
226 + }
227 + }
228 +
229 + /// <summary>
230 + /// Determines whether the data contains a specified item.
231 + /// </summary>
232 + /// <param name="item">The data item to locate.</param>
233 + /// <returns>true if the data contains the item; otherwise, false</returns>
234 + public bool Contains(KeyValuePair<string, string> item)
235 + {
236 + return this.data.Contains(item);
237 + }
238 +
239 + /// <summary>
240 + /// Copies the data to an array, starting at a particular array index.
241 + /// </summary>
242 + /// <param name="array">Destination array.</param>
243 + /// <param name="arrayIndex">Index in the array at which copying begins.</param>
244 + public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
245 + {
246 + this.data.CopyTo(array, arrayIndex);
247 + }
248 +
249 + /// <summary>
250 + /// Gets the number of items in the data.
251 + /// </summary>
252 + public int Count
253 + {
254 + get
255 + {
256 + return this.data.Count;
257 + }
258 + }
259 +
260 + /// <summary>
261 + /// Gets a value indicating whether the data is read-only.
262 + /// </summary>
263 + public bool IsReadOnly
264 + {
265 + get
266 + {
267 + return false;
268 + }
269 + }
270 +
271 + /// <summary>
272 + /// Removes an item from the data.
273 + /// </summary>
274 + /// <param name="item">The item to remove.</param>
275 + /// <returns>true if the item was successfully removed from the data;
276 + /// false if the item was not found</returns>
277 + public bool Remove(KeyValuePair<string, string> item)
278 + {
279 + return this.data.Remove(item);
280 + }
281 +
282 + /// <summary>
283 + /// Returns an enumerator that iterates through the collection.
284 + /// </summary>
285 + /// <returns>An enumerator that can be used to iterate through the collection.</returns>
286 + public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
287 + {
288 + return this.data.GetEnumerator();
289 + }
290 +
291 + /// <summary>
292 + /// Returns an enumerator that iterates through the collection.
293 + /// </summary>
294 + /// <returns>An enumerator that can be used to iterate through the collection.</returns>
295 + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
296 + {
297 + return ((System.Collections.IEnumerable) this.data).GetEnumerator();
298 + }
299 +
300 + /// <summary>
301 + /// Gets a string representation of the data suitable for persisting in a property.
302 + /// </summary>
303 + /// <returns>Data string in the form "Key1=Value1;Key2=Value2"</returns>
304 + public override string ToString()
305 + {
306 + StringBuilder buf = new StringBuilder();
307 +
308 + foreach (KeyValuePair<string, string> item in this.data)
309 + {
310 + if (buf.Length > 0)
311 + {
312 + buf.Append(CustomActionData.DataSeparator);
313 + }
314 +
315 + buf.Append(item.Key);
316 +
317 + if (item.Value != null)
318 + {
319 + buf.Append(CustomActionData.KeyValueSeparator);
320 + buf.Append(CustomActionData.Escape(item.Value));
321 + }
322 + }
323 +
324 + return buf.ToString();
325 + }
326 +
327 + /// <summary>
328 + /// Ensures that a key contains valid characters.
329 + /// </summary>
330 + /// <param name="key">key to be validated</param>
331 + /// <exception cref="ArgumentException">the key does not consist solely of letters,
332 + /// numbers, and the period, underscore, and space characters.</exception>
333 + private static void ValidateKey(string key)
334 + {
335 + if (String.IsNullOrEmpty(key))
336 + {
337 + throw new ArgumentNullException("key");
338 + }
339 +
340 + for (int i = 0; i < key.Length; i++)
341 + {
342 + char c = key[i];
343 + if (!Char.IsLetterOrDigit(c) && c != '_' && c != '.' &&
344 + !(i > 0 && i < key.Length - 1 && c == ' '))
345 + {
346 + throw new ArgumentOutOfRangeException("key");
347 + }
348 + }
349 + }
350 +
351 + /// <summary>
352 + /// Serializes a value into an XML string.
353 + /// </summary>
354 + /// <typeparam name="T">Type of the value.</typeparam>
355 + /// <param name="value">Value to be serialized.</param>
356 + /// <returns>Serialized value data as a string.</returns>
357 + private static string Serialize<T>(T value)
358 + {
359 + XmlWriterSettings xws = new XmlWriterSettings();
360 + xws.OmitXmlDeclaration = true;
361 +
362 + StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
363 + using (XmlWriter xw = XmlWriter.Create(sw, xws))
364 + {
365 + XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
366 + ns.Add(string.Empty, String.Empty); // Prevent output of any namespaces
367 +
368 + XmlSerializer ser = new XmlSerializer(typeof(T));
369 + ser.Serialize(xw, value, ns);
370 +
371 + return sw.ToString();
372 + }
373 + }
374 +
375 + /// <summary>
376 + /// Deserializes a value from an XML string.
377 + /// </summary>
378 + /// <typeparam name="T">Expected type of the value.</typeparam>
379 + /// <param name="value">Serialized value data.</param>
380 + /// <returns>Deserialized value object.</returns>
381 + private static T Deserialize<T>(string value)
382 + {
383 + StringReader sr = new StringReader(value);
384 + using (XmlReader xr = XmlReader.Create(sr))
385 + {
386 + XmlSerializer ser = new XmlSerializer(typeof(T));
387 + return (T) ser.Deserialize(xr);
388 + }
389 + }
390 +
391 + /// <summary>
392 + /// Escapes a value string by doubling any data-separator (semicolon) characters.
393 + /// </summary>
394 + /// <param name="value"></param>
395 + /// <returns>Escaped value string</returns>
396 + private static string Escape(string value)
397 + {
398 + value = value.Replace(String.Empty + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator);
399 + return value;
400 + }
401 +
402 + /// <summary>
403 + /// Unescapes a value string by undoubling any doubled data-separator (semicolon) characters.
404 + /// </summary>
405 + /// <param name="value"></param>
406 + /// <returns>Unescaped value string</returns>
407 + private static string Unescape(string value)
408 + {
409 + value = value.Replace(String.Empty + CustomActionData.DataSeparator + CustomActionData.DataSeparator, String.Empty + CustomActionData.DataSeparator);
410 + return value;
411 + }
412 +
413 + /// <summary>
414 + /// Loads key-value pairs from a string into the data collection.
415 + /// </summary>
416 + /// <param name="keyValueList">key-value pair list of the form returned by <see cref="ToString"/></param>
417 + private void Parse(string keyValueList)
418 + {
419 + int itemStart = 0;
420 + while (itemStart < keyValueList.Length)
421 + {
422 + // Find the next non-escaped data separator.
423 + int semi = itemStart - 2;
424 + do
425 + {
426 + semi = keyValueList.IndexOf(CustomActionData.DataSeparator, semi + 2);
427 + }
428 + while (semi >= 0 && semi < keyValueList.Length - 1 && keyValueList[semi + 1] == CustomActionData.DataSeparator);
429 +
430 + if (semi < 0)
431 + {
432 + semi = keyValueList.Length;
433 + }
434 +
435 + // Find the next non-escaped key-value separator.
436 + int equals = itemStart - 2;
437 + do
438 + {
439 + equals = keyValueList.IndexOf(CustomActionData.KeyValueSeparator, equals + 2);
440 + }
441 + while (equals >= 0 && equals < keyValueList.Length - 1 && keyValueList[equals + 1] == CustomActionData.KeyValueSeparator);
442 +
443 + if (equals < 0 || equals > semi)
444 + {
445 + equals = semi;
446 + }
447 +
448 + string key = keyValueList.Substring(itemStart, equals - itemStart);
449 + string value = null;
450 +
451 + // If there's a key-value separator before the next data separator, then the item has a value.
452 + if (equals < semi)
453 + {
454 + value = keyValueList.Substring(equals + 1, semi - (equals + 1));
455 + value = CustomActionData.Unescape(value);
456 + }
457 +
458 + // Add non-duplicate items to the collection.
459 + if (key.Length > 0 && !this.data.ContainsKey(key))
460 + {
461 + this.data.Add(key, value);
462 + }
463 +
464 + // Move past the data separator to the next item.
465 + itemStart = semi + 1;
466 + }
467 + }
468 + }
469 +}
src/dtf/WixToolset.Dtf.sln new
+281
@@ -0,0 +1,281 @@
1 +
2 +Microsoft Visual Studio Solution File, Format Version 12.00
3 +# Visual Studio 15
4 +VisualStudioVersion = 15.0.26730.8
5 +MinimumVisualStudioVersion = 15.0.26124.0
6 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression", "src\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}"
7 +EndProject
8 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Cab", "src\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}"
9 +EndProject
10 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Zip", "src\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}"
11 +EndProject
12 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Resources", "src\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}"
13 +EndProject
14 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller", "src\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}"
15 +EndProject
16 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Linq", "src\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}"
17 +EndProject
18 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Package", "src\WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}"
19 +EndProject
20 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "src\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}"
21 +EndProject
22 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "src\WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}"
23 +EndProject
24 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "src\WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}"
25 +EndProject
26 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "src\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}"
27 +EndProject
28 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "src\WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}"
29 +EndProject
30 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "src\WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}"
31 +EndProject
32 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{A988A768-200F-408F-AE3B-5D9B14AA48EE}"
33 +EndProject
34 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SfxCA", "src\Tools\SfxCA\SfxCA.vcxproj", "{55D5BA28-D427-4F53-80C2-FE9EF23C1553}"
35 +EndProject
36 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MakeSfxCA", "src\Tools\MakeSfxCA\MakeSfxCA.csproj", "{3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}"
37 +EndProject
38 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{4A47EC94-8234-4479-87BC-3D924DB7AA4E}"
39 +EndProject
40 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCA", "src\Samples\ManagedCA\ManagedCA.csproj", "{DB9E5F02-8241-440A-9B60-980EB5B42B13}"
41 +EndProject
42 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedUI", "src\Samples\EmbeddedUI\EmbeddedUI.csproj", "{864B8C50-7895-4485-AC89-900D86FD8C0D}"
43 +EndProject
44 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.MSBuild", "src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj", "{E7A00377-A0B5-400F-8337-C0814AAC7153}"
45 +EndProject
46 +Global
47 + GlobalSection(SolutionConfigurationPlatforms) = preSolution
48 + Debug|Any CPU = Debug|Any CPU
49 + Debug|x64 = Debug|x64
50 + Debug|x86 = Debug|x86
51 + Release|Any CPU = Release|Any CPU
52 + Release|x64 = Release|x64
53 + Release|x86 = Release|x86
54 + EndGlobalSection
55 + GlobalSection(ProjectConfigurationPlatforms) = postSolution
56 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x64.ActiveCfg = Debug|Any CPU
59 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x64.Build.0 = Debug|Any CPU
60 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x86.ActiveCfg = Debug|Any CPU
61 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Debug|x86.Build.0 = Debug|Any CPU
62 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|Any CPU.Build.0 = Release|Any CPU
64 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x64.ActiveCfg = Release|Any CPU
65 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x64.Build.0 = Release|Any CPU
66 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x86.ActiveCfg = Release|Any CPU
67 + {2D62850C-9F81-4BE9-BDF3-9379389C8F7B}.Release|x86.Build.0 = Release|Any CPU
68 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x64.ActiveCfg = Debug|Any CPU
71 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x64.Build.0 = Debug|Any CPU
72 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x86.ActiveCfg = Debug|Any CPU
73 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Debug|x86.Build.0 = Debug|Any CPU
74 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|Any CPU.Build.0 = Release|Any CPU
76 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x64.ActiveCfg = Release|Any CPU
77 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x64.Build.0 = Release|Any CPU
78 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x86.ActiveCfg = Release|Any CPU
79 + {15895FD1-DD68-407B-8717-08F6DD14F02C}.Release|x86.Build.0 = Release|Any CPU
80 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x64.ActiveCfg = Debug|Any CPU
83 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x64.Build.0 = Debug|Any CPU
84 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x86.ActiveCfg = Debug|Any CPU
85 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Debug|x86.Build.0 = Debug|Any CPU
86 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|Any CPU.ActiveCfg = Release|Any CPU
87 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|Any CPU.Build.0 = Release|Any CPU
88 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x64.ActiveCfg = Release|Any CPU
89 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x64.Build.0 = Release|Any CPU
90 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x86.ActiveCfg = Release|Any CPU
91 + {261F2857-B521-42A4-A3E0-B5165F225E50}.Release|x86.Build.0 = Release|Any CPU
92 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|Any CPU.Build.0 = Debug|Any CPU
94 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x64.ActiveCfg = Debug|Any CPU
95 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x64.Build.0 = Debug|Any CPU
96 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x86.ActiveCfg = Debug|Any CPU
97 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Debug|x86.Build.0 = Debug|Any CPU
98 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|Any CPU.ActiveCfg = Release|Any CPU
99 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|Any CPU.Build.0 = Release|Any CPU
100 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x64.ActiveCfg = Release|Any CPU
101 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x64.Build.0 = Release|Any CPU
102 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x86.ActiveCfg = Release|Any CPU
103 + {44931ECB-8D6F-4C12-A872-64E261B6A98E}.Release|x86.Build.0 = Release|Any CPU
104 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
106 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x64.ActiveCfg = Debug|Any CPU
107 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x64.Build.0 = Debug|Any CPU
108 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x86.ActiveCfg = Debug|Any CPU
109 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Debug|x86.Build.0 = Debug|Any CPU
110 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
111 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|Any CPU.Build.0 = Release|Any CPU
112 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x64.ActiveCfg = Release|Any CPU
113 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x64.Build.0 = Release|Any CPU
114 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x86.ActiveCfg = Release|Any CPU
115 + {24121677-0ED0-41B5-833F-1B9A18E87BF4}.Release|x86.Build.0 = Release|Any CPU
116 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
117 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
118 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x64.ActiveCfg = Debug|Any CPU
119 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x64.Build.0 = Debug|Any CPU
120 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x86.ActiveCfg = Debug|Any CPU
121 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Debug|x86.Build.0 = Debug|Any CPU
122 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
123 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|Any CPU.Build.0 = Release|Any CPU
124 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x64.ActiveCfg = Release|Any CPU
125 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x64.Build.0 = Release|Any CPU
126 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x86.ActiveCfg = Release|Any CPU
127 + {CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}.Release|x86.Build.0 = Release|Any CPU
128 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
129 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
130 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x64.ActiveCfg = Debug|Any CPU
131 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x64.Build.0 = Debug|Any CPU
132 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x86.ActiveCfg = Debug|Any CPU
133 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Debug|x86.Build.0 = Debug|Any CPU
134 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
135 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|Any CPU.Build.0 = Release|Any CPU
136 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x64.ActiveCfg = Release|Any CPU
137 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x64.Build.0 = Release|Any CPU
138 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x86.ActiveCfg = Release|Any CPU
139 + {1A9940A7-3E29-4428-B753-C4CC66058F1A}.Release|x86.Build.0 = Release|Any CPU
140 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
141 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
142 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.ActiveCfg = Debug|Any CPU
143 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.Build.0 = Debug|Any CPU
144 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.ActiveCfg = Debug|Any CPU
145 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.Build.0 = Debug|Any CPU
146 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
147 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Release|Any CPU
148 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.ActiveCfg = Release|Any CPU
149 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.Build.0 = Release|Any CPU
150 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.ActiveCfg = Release|Any CPU
151 + {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.Build.0 = Release|Any CPU
152 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
153 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|Any CPU.Build.0 = Debug|Any CPU
154 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.ActiveCfg = Debug|Any CPU
155 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.Build.0 = Debug|Any CPU
156 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.ActiveCfg = Debug|Any CPU
157 + {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.Build.0 = Debug|Any CPU
158 + {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Release|Any CPU
159 + {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Release|Any CPU
160 + {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.ActiveCfg = Release|Any CPU
161 + {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.Build.0 = Release|Any CPU
162 + {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.ActiveCfg = Release|Any CPU
163 + {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.Build.0 = Release|Any CPU
164 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
165 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|Any CPU.Build.0 = Debug|Any CPU
166 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.ActiveCfg = Debug|Any CPU
167 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.Build.0 = Debug|Any CPU
168 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.ActiveCfg = Debug|Any CPU
169 + {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.Build.0 = Debug|Any CPU
170 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Release|Any CPU
171 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Release|Any CPU
172 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.ActiveCfg = Release|Any CPU
173 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.Build.0 = Release|Any CPU
174 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.ActiveCfg = Release|Any CPU
175 + {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.Build.0 = Release|Any CPU
176 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
177 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|Any CPU.Build.0 = Debug|Any CPU
178 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.ActiveCfg = Debug|Any CPU
179 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.Build.0 = Debug|Any CPU
180 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.ActiveCfg = Debug|Any CPU
181 + {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.Build.0 = Debug|Any CPU
182 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Release|Any CPU
183 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Release|Any CPU
184 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.ActiveCfg = Release|Any CPU
185 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.Build.0 = Release|Any CPU
186 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.ActiveCfg = Release|Any CPU
187 + {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.Build.0 = Release|Any CPU
188 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
189 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
190 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.ActiveCfg = Debug|Any CPU
191 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.Build.0 = Debug|Any CPU
192 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.ActiveCfg = Debug|Any CPU
193 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.Build.0 = Debug|Any CPU
194 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
195 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Release|Any CPU
196 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.ActiveCfg = Release|Any CPU
197 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.Build.0 = Release|Any CPU
198 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.ActiveCfg = Release|Any CPU
199 + {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.Build.0 = Release|Any CPU
200 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
201 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|Any CPU.Build.0 = Debug|Any CPU
202 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.ActiveCfg = Debug|Any CPU
203 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.Build.0 = Debug|Any CPU
204 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.ActiveCfg = Debug|Any CPU
205 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.Build.0 = Debug|Any CPU
206 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Release|Any CPU
207 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Release|Any CPU
208 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.ActiveCfg = Release|Any CPU
209 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.Build.0 = Release|Any CPU
210 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.ActiveCfg = Release|Any CPU
211 + {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.Build.0 = Release|Any CPU
212 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|Any CPU.ActiveCfg = Debug|Win32
213 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x64.ActiveCfg = Debug|Win32
214 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x86.ActiveCfg = Debug|Win32
215 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Debug|x86.Build.0 = Debug|Win32
216 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|Any CPU.ActiveCfg = Release|Win32
217 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x64.ActiveCfg = Release|x64
218 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x86.ActiveCfg = Release|Win32
219 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553}.Release|x86.Build.0 = Release|Win32
220 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
221 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|Any CPU.Build.0 = Debug|Any CPU
222 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x64.ActiveCfg = Debug|Any CPU
223 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x64.Build.0 = Debug|Any CPU
224 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x86.ActiveCfg = Debug|Any CPU
225 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Debug|x86.Build.0 = Debug|Any CPU
226 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|Any CPU.ActiveCfg = Release|Any CPU
227 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|Any CPU.Build.0 = Release|Any CPU
228 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x64.ActiveCfg = Release|Any CPU
229 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x64.Build.0 = Release|Any CPU
230 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x86.ActiveCfg = Release|Any CPU
231 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B}.Release|x86.Build.0 = Release|Any CPU
232 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
233 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|Any CPU.Build.0 = Debug|Any CPU
234 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x64.ActiveCfg = Debug|Any CPU
235 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x64.Build.0 = Debug|Any CPU
236 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x86.ActiveCfg = Debug|Any CPU
237 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Debug|x86.Build.0 = Debug|Any CPU
238 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|Any CPU.ActiveCfg = Release|Any CPU
239 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|Any CPU.Build.0 = Release|Any CPU
240 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x64.ActiveCfg = Release|Any CPU
241 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x64.Build.0 = Release|Any CPU
242 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x86.ActiveCfg = Release|Any CPU
243 + {DB9E5F02-8241-440A-9B60-980EB5B42B13}.Release|x86.Build.0 = Release|Any CPU
244 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
245 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
246 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x64.ActiveCfg = Debug|Any CPU
247 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x64.Build.0 = Debug|Any CPU
248 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x86.ActiveCfg = Debug|Any CPU
249 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Debug|x86.Build.0 = Debug|Any CPU
250 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
251 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|Any CPU.Build.0 = Release|Any CPU
252 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x64.ActiveCfg = Release|Any CPU
253 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x64.Build.0 = Release|Any CPU
254 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x86.ActiveCfg = Release|Any CPU
255 + {864B8C50-7895-4485-AC89-900D86FD8C0D}.Release|x86.Build.0 = Release|Any CPU
256 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
257 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.Build.0 = Debug|Any CPU
258 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.ActiveCfg = Debug|Any CPU
259 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.Build.0 = Debug|Any CPU
260 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.ActiveCfg = Debug|Any CPU
261 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.Build.0 = Debug|Any CPU
262 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.ActiveCfg = Release|Any CPU
263 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.Build.0 = Release|Any CPU
264 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.ActiveCfg = Release|Any CPU
265 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.Build.0 = Release|Any CPU
266 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.ActiveCfg = Release|Any CPU
267 + {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.Build.0 = Release|Any CPU
268 + EndGlobalSection
269 + GlobalSection(SolutionProperties) = preSolution
270 + HideSolutionNode = FALSE
271 + EndGlobalSection
272 + GlobalSection(NestedProjects) = preSolution
273 + {55D5BA28-D427-4F53-80C2-FE9EF23C1553} = {A988A768-200F-408F-AE3B-5D9B14AA48EE}
274 + {3F246CE0-153D-4AC3-B6AC-5EAD8E2AD04B} = {A988A768-200F-408F-AE3B-5D9B14AA48EE}
275 + {DB9E5F02-8241-440A-9B60-980EB5B42B13} = {4A47EC94-8234-4479-87BC-3D924DB7AA4E}
276 + {864B8C50-7895-4485-AC89-900D86FD8C0D} = {4A47EC94-8234-4479-87BC-3D924DB7AA4E}
277 + EndGlobalSection
278 + GlobalSection(ExtensibilityGlobals) = postSolution
279 + SolutionGuid = {BB57C98D-C0C2-4805-AED3-C19B47759DBD}
280 + EndGlobalSection
281 +EndGlobal
src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs new
+1165
@@ -0,0 +1,1165 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Threading;
9 + using System.Collections.Generic;
10 + using System.Runtime.Serialization;
11 + using System.Runtime.Serialization.Formatters.Binary;
12 + using Microsoft.VisualStudio.TestTools.UnitTesting;
13 + using WixToolset.Dtf.Compression;
14 + using WixToolset.Dtf.Compression.Cab;
15 +
16 + [TestClass]
17 + public class CabTest
18 + {
19 + public CabTest()
20 + {
21 + }
22 +
23 + [TestInitialize]
24 + public void Initialize()
25 + {
26 + }
27 +
28 + [TestCleanup]
29 + public void Cleanup()
30 + {
31 + }
32 +
33 + [TestMethod]
34 + public void CabinetMultithread()
35 + {
36 + this.multithreadExceptions = new List<Exception>();
37 +
38 + const int threadCount = 10;
39 + IList<Thread> threads = new List<Thread>(threadCount);
40 +
41 + for (int i = 0; i < threadCount; i++)
42 + {
43 + Thread thread = new Thread(new ThreadStart(this.CabinetMultithreadWorker));
44 + thread.Name = "CabinetMultithreadWorker_" + i;
45 + threads.Add(thread);
46 + }
47 +
48 + foreach (Thread thread in threads)
49 + {
50 + thread.Start();
51 + }
52 +
53 + foreach (Thread thread in threads)
54 + {
55 + thread.Join();
56 + }
57 +
58 + foreach (Exception ex in this.multithreadExceptions)
59 + {
60 + Console.WriteLine();
61 + Console.WriteLine(ex);
62 + }
63 + Assert.AreEqual<int>(0, this.multithreadExceptions.Count);
64 + }
65 +
66 + private IList<Exception> multithreadExceptions;
67 +
68 + private void CabinetMultithreadWorker()
69 + {
70 + try
71 + {
72 + string threadName = Thread.CurrentThread.Name;
73 + int threadNumber = Int32.Parse(threadName.Substring(threadName.IndexOf('_') + 1));
74 + this.RunCabinetPackUnpack(100, 10240 + threadNumber, 0, 0, CompressionLevel.Normal);
75 + }
76 + catch (Exception ex)
77 + {
78 + this.multithreadExceptions.Add(ex);
79 + }
80 + }
81 +
82 + [TestMethod]
83 + public void CabinetFileCounts()
84 + {
85 + this.RunCabinetPackUnpack(0, 10, 0, 0, CompressionLevel.Normal);
86 + this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal);
87 + this.RunCabinetPackUnpack(100, 10, 0, 0, CompressionLevel.Normal);
88 + }
89 +
90 + [TestMethod]
91 + [Ignore] // Takes ~5 minutes and 66000 is over the 65535 limit anyway.
92 + public void CabinetExtremeFileCounts()
93 + {
94 + this.RunCabinetPackUnpack(66000, 10);
95 + }
96 +
97 + [TestMethod]
98 + public void CabinetFileSizes()
99 + {
100 + this.RunCabinetPackUnpack(1, 0, 0, 0, CompressionLevel.Normal);
101 + this.RunCabinetPackUnpack(1, 1, 0, 0, CompressionLevel.Normal);
102 + this.RunCabinetPackUnpack(1, 2, 0, 0, CompressionLevel.Normal);
103 + this.RunCabinetPackUnpack(1, 3, 0, 0, CompressionLevel.Normal);
104 + this.RunCabinetPackUnpack(1, 4, 0, 0, CompressionLevel.Normal);
105 + this.RunCabinetPackUnpack(1, 5, 0, 0, CompressionLevel.Normal);
106 + this.RunCabinetPackUnpack(1, 6, 0, 0, CompressionLevel.Normal);
107 + // Skip file sizes 7-9: see "buggy" file sizes test below.
108 + this.RunCabinetPackUnpack(1, 10, 0, 0, CompressionLevel.Normal);
109 + this.RunCabinetPackUnpack(1, 11, 0, 0, CompressionLevel.Normal);
110 + this.RunCabinetPackUnpack(1, 12, 0, 0, CompressionLevel.Normal);
111 + this.RunCabinetPackUnpack(1, 100 * 1024, 0, 0, CompressionLevel.Normal);
112 + this.RunCabinetPackUnpack(1, 10 * 1024 * 1024, 0, 0, CompressionLevel.Normal);
113 + }
114 +
115 + [TestMethod]
116 + public void CabinetBuggyFileSizes()
117 + {
118 + // Windows' cabinet.dll has a known bug (#55001 in Windows OS Bugs)
119 + // LZX compression causes an AV with file sizes of 7, 8, or 9 bytes.
120 + try
121 + {
122 + this.RunCabinetPackUnpack(1, 7, 0, 0, CompressionLevel.Normal);
123 + this.RunCabinetPackUnpack(1, 8, 0, 0, CompressionLevel.Normal);
124 + this.RunCabinetPackUnpack(1, 9, 0, 0, CompressionLevel.Normal);
125 + }
126 + catch (AccessViolationException)
127 + {
128 + Assert.Fail("Known 7,8,9 file size bug detected in Windows' cabinet.dll.");
129 + }
130 + }
131 +
132 + [Timeout(36000000), TestMethod]
133 + [Ignore] // Takes too long to run regularly.
134 + public void CabinetExtremeFileSizes()
135 + {
136 + this.RunCabinetPackUnpack(10, 512L * 1024 * 1024); // 5GB
137 + //this.RunCabinetPackUnpack(1, 5L * 1024 * 1024 * 1024); // 5GB
138 + }
139 +
140 + [TestMethod]
141 + public void CabinetFolders()
142 + {
143 + this.RunCabinetPackUnpack(0, 10, 1, 0, CompressionLevel.Normal);
144 + this.RunCabinetPackUnpack(1, 10, 1, 0, CompressionLevel.Normal);
145 + this.RunCabinetPackUnpack(100, 10, 1, 0, CompressionLevel.Normal);
146 +
147 + IList<ArchiveFileInfo> fileInfo;
148 + fileInfo = this.RunCabinetPackUnpack(7, 100 * 1024, 250 * 1024, 0, CompressionLevel.None);
149 + Assert.AreEqual<int>(2, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
150 + "Testing whether cabinet has the correct # of folders.");
151 +
152 + fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 250 * 1024, 0, CompressionLevel.None);
153 + Assert.AreEqual<int>(3, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
154 + "Testing whether cabinet has the correct # of folders.");
155 +
156 + fileInfo = this.RunCabinetPackUnpack(2, 100 * 1024, 40 * 1024, 0, CompressionLevel.None);
157 + Assert.AreEqual<int>(1, ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber,
158 + "Testing whether cabinet has the correct # of folders.");
159 + }
160 +
161 + [TestMethod]
162 + public void CabinetArchiveCounts()
163 + {
164 + IList<ArchiveFileInfo> fileInfo;
165 + fileInfo = this.RunCabinetPackUnpack(10, 100 * 1024, 0, 400 * 1024, CompressionLevel.None);
166 + Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
167 + "Testing whether archive spans the correct # of cab files.");
168 +
169 + fileInfo = this.RunCabinetPackUnpack(2, 90 * 1024, 0, 40 * 1024, CompressionLevel.None);
170 + Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
171 + "Testing whether archive spans the correct # of cab files.");
172 + }
173 +
174 + [TestMethod]
175 + public void CabinetProgress()
176 + {
177 + CompressionTestUtil.ExpectedProgress = new List<int[]>(new int[][] {
178 + // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs
179 + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 },
180 + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 },
181 + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 },
182 + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 },
183 + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 1 },
184 + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 1 },
185 + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 1 },
186 + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 1 },
187 + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 1 },
188 + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 1 },
189 + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 1 },
190 + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 1 },
191 + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 1 },
192 + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 0, 1 },
193 + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 0, 1 },
194 + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 0, 1 },
195 + new int[] { (int) ArchiveProgressType.StartArchive, 7, 15, 3, 0, 1 },
196 + new int[] { (int) ArchiveProgressType.FinishArchive, 7, 15, 3, 0, 1 },
197 + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 2 },
198 + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 2 },
199 + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 2 },
200 + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 2 },
201 + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 2 },
202 + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 2 },
203 + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 2 },
204 + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 2 },
205 + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 2 },
206 + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 1, 2 },
207 + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 1, 2 },
208 + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 1, 2 },
209 + new int[] { (int) ArchiveProgressType.StartArchive, 13, 15, 6, 1, 2 },
210 + new int[] { (int) ArchiveProgressType.FinishArchive, 13, 15, 6, 1, 2 },
211 + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 },
212 + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 },
213 + new int[] { (int) ArchiveProgressType.StartArchive, 14, 15, 7, 2, 3 },
214 + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 },
215 + // StatusType, CurFile,TotalFiles,CurFolder,CurCab,TotalCabs
216 + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 },
217 + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 },
218 + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 },
219 + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 },
220 + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 },
221 + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 1, 0, 3 },
222 + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 1, 0, 3 },
223 + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 1, 0, 3 },
224 + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 1, 0, 3 },
225 + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 2, 0, 3 },
226 + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 2, 0, 3 },
227 + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 2, 0, 3 },
228 + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 2, 0, 3 },
229 + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 3, 0, 3 },
230 + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 3, 0, 3 },
231 + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 3, 1, 3 },
232 + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 3, 1, 3 },
233 + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 3, 1, 3 },
234 + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 3, 1, 3 },
235 + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 4, 1, 3 },
236 + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 4, 1, 3 },
237 + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 4, 1, 3 },
238 + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 4, 1, 3 },
239 + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 5, 1, 3 },
240 + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 5, 1, 3 },
241 + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 5, 1, 3 },
242 + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 5, 1, 3 },
243 + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 6, 1, 3 },
244 + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 6, 1, 3 },
245 + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 6, 2, 3 },
246 + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 6, 2, 3 },
247 + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 6, 2, 3 },
248 + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 6, 2, 3 },
249 + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 7, 2, 3 },
250 + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 7, 2, 3 },
251 + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 7, 2, 3 },
252 + });
253 +
254 + try
255 + {
256 + this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024, CompressionLevel.None);
257 + }
258 + finally
259 + {
260 + CompressionTestUtil.ExpectedProgress = null;
261 + }
262 + }
263 +
264 + [TestMethod]
265 + public void CabArchiveSizeParam()
266 + {
267 + Console.WriteLine("Testing various values for the maxArchiveSize parameter.");
268 + this.RunCabinetPackUnpack(5, 1024, 0, Int64.MinValue);
269 + this.RunCabinetPackUnpack(5, 1024, 0, -1);
270 + this.RunCabinetPackUnpack(5, 10, 0, 2);
271 + this.RunCabinetPackUnpack(5, 100, 0, 256);
272 + this.RunCabinetPackUnpack(5, 24000, 0, 32768);
273 + this.RunCabinetPackUnpack(5, 1024, 0, Int64.MaxValue);
274 + }
275 +
276 + [TestMethod]
277 + public void CabFolderSizeParam()
278 + {
279 + Console.WriteLine("Testing various values for the maxFolderSize parameter.");
280 + this.RunCabinetPackUnpack(5, 10, Int64.MinValue, 0);
281 + this.RunCabinetPackUnpack(5, 10, -1, 0);
282 + this.RunCabinetPackUnpack(5, 10, 2, 0);
283 + this.RunCabinetPackUnpack(5, 10, 16, 0);
284 + this.RunCabinetPackUnpack(5, 10, 100, 0);
285 + this.RunCabinetPackUnpack(5, 10, Int64.MaxValue, 0);
286 + }
287 +
288 + [TestMethod]
289 + public void CabCompLevelParam()
290 + {
291 + Console.WriteLine("Testing various values for the compressionLevel parameter.");
292 + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.None);
293 + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Min);
294 + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Normal);
295 + this.RunCabinetPackUnpack(5, 1024, 0, 0, CompressionLevel.Max);
296 + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.None - 1));
297 + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1));
298 + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MinValue);
299 + this.RunCabinetPackUnpack(5, 1024, 0, 0, (CompressionLevel) Int32.MaxValue);
300 + }
301 +
302 + [TestMethod]
303 + public void CabEngineNullParams()
304 + {
305 + string[] testFiles = new string[] { "test.txt" };
306 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.cab", null, null);
307 +
308 + using (CabEngine cabEngine = new CabEngine())
309 + {
310 + cabEngine.CompressionLevel = CompressionLevel.None;
311 +
312 + CompressionTestUtil.TestCompressionEngineNullParams(
313 + cabEngine, streamContext, testFiles);
314 + }
315 + }
316 +
317 + [TestMethod]
318 + public void CabBadPackStreamContexts()
319 + {
320 + string[] testFiles = new string[] { "test.txt" };
321 + CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000);
322 +
323 + using (CabEngine cabEngine = new CabEngine())
324 + {
325 + cabEngine.CompressionLevel = CompressionLevel.None;
326 +
327 + CompressionTestUtil.TestBadPackStreamContexts(cabEngine, "test.cab", testFiles);
328 + }
329 + }
330 +
331 + [TestMethod]
332 + public void CabEngineNoTempFileTest()
333 + {
334 + int txtSize = 10240;
335 + CompressionTestUtil.GenerateRandomFile("testnotemp.txt", 0, txtSize);
336 +
337 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("testnotemp.cab", null, null);
338 +
339 + using (CabEngine cabEngine = new CabEngine())
340 + {
341 + cabEngine.UseTempFiles = false;
342 + cabEngine.Pack(streamContext, new string[] { "testnotemp.txt" });
343 + }
344 +
345 + new CabInfo("testnotemp.cab").UnpackFile("testnotemp.txt", "testnotemp2.txt");
346 + Assert.AreEqual(txtSize, new FileInfo("testnotemp2.txt").Length);
347 + }
348 +
349 + [TestMethod]
350 + public void CabExtractorIsCabinet()
351 + {
352 + int txtSize = 10240;
353 + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
354 + new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null);
355 + using (CabEngine cabEngine = new CabEngine())
356 + {
357 + bool isCab;
358 + using (Stream fileStream = File.OpenRead("test.txt"))
359 + {
360 + isCab = cabEngine.IsArchive(fileStream);
361 + }
362 + Assert.IsFalse(isCab);
363 + using (Stream cabStream = File.OpenRead("test.cab"))
364 + {
365 + isCab = cabEngine.IsArchive(cabStream);
366 + }
367 + Assert.IsTrue(isCab);
368 + using (Stream cabStream = File.OpenRead("test.cab"))
369 + {
370 + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
371 + {
372 + fileStream.Seek(0, SeekOrigin.End);
373 + byte[] buf = new byte[1024];
374 + int count;
375 + while ((count = cabStream.Read(buf, 0, buf.Length)) > 0)
376 + {
377 + fileStream.Write(buf, 0, count);
378 + }
379 + fileStream.Seek(0, SeekOrigin.Begin);
380 + isCab = cabEngine.IsArchive(fileStream);
381 + }
382 + }
383 + Assert.IsFalse(isCab);
384 + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
385 + {
386 + fileStream.Write(new byte[] { (byte) 'M', (byte) 'S', (byte) 'C', (byte) 'F' }, 0, 4);
387 + fileStream.Seek(0, SeekOrigin.Begin);
388 + isCab = cabEngine.IsArchive(fileStream);
389 + }
390 + Assert.IsFalse(isCab);
391 + }
392 + }
393 +
394 + [TestMethod]
395 + public void CabExtractorFindOffset()
396 + {
397 + int txtSize = 10240;
398 + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
399 + new CabInfo("test.cab").PackFiles(null, new string[] { "test.txt" }, null);
400 + using (CabEngine cabEngine = new CabEngine())
401 + {
402 + long offset;
403 + using (Stream fileStream = File.OpenRead("test.txt"))
404 + {
405 + offset = cabEngine.FindArchiveOffset(fileStream);
406 + }
407 + Assert.AreEqual<long>(-1, offset);
408 + using (Stream cabStream = File.OpenRead("test.cab"))
409 + {
410 + using (Stream fileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite))
411 + {
412 + fileStream.Seek(0, SeekOrigin.End);
413 + byte[] buf = new byte[1024];
414 + int count;
415 + while ((count = cabStream.Read(buf, 0, buf.Length)) > 0)
416 + {
417 + fileStream.Write(buf, 0, count);
418 + }
419 + fileStream.Seek(0, SeekOrigin.Begin);
420 + offset = cabEngine.FindArchiveOffset(fileStream);
421 + }
422 + }
423 + Assert.AreEqual<long>(txtSize, offset);
424 + }
425 + }
426 +
427 + [TestMethod]
428 + public void CabExtractorGetFiles()
429 + {
430 + IList<ArchiveFileInfo> fileInfo;
431 + CabInfo cabInfo = new CabInfo("testgetfiles.cab");
432 + int txtSize = 10240;
433 + CompressionTestUtil.GenerateRandomFile("testgetfiles0.txt", 0, txtSize);
434 + CompressionTestUtil.GenerateRandomFile("testgetfiles1.txt", 1, txtSize);
435 + cabInfo.PackFiles(null, new string[] { "testgetfiles0.txt", "testgetfiles1.txt" }, null);
436 + using (CabEngine cabEngine = new CabEngine())
437 + {
438 + IList<string> files;
439 + using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
440 + {
441 + files = cabEngine.GetFiles(cabStream);
442 + }
443 + Assert.IsNotNull(files);
444 + Assert.AreEqual<int>(2, files.Count);
445 + Assert.AreEqual<string>("testgetfiles0.txt", files[0]);
446 + Assert.AreEqual<string>("testgetfiles1.txt", files[1]);
447 +
448 + using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
449 + {
450 + files = cabEngine.GetFiles(new ArchiveFileStreamContext("testgetfiles.cab"), null);
451 + }
452 + Assert.IsNotNull(files);
453 + Assert.AreEqual<int>(2, files.Count);
454 + Assert.AreEqual<string>("testgetfiles0.txt", files[0]);
455 + Assert.AreEqual<string>("testgetfiles1.txt", files[1]);
456 +
457 + using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
458 + {
459 + fileInfo = cabEngine.GetFileInfo(cabStream);
460 + }
461 + Assert.IsNotNull(fileInfo);
462 + Assert.AreEqual<int>(2, fileInfo.Count);
463 + Assert.AreEqual<string>("testgetfiles0.txt", fileInfo[0].Name);
464 + Assert.AreEqual<string>("testgetfiles1.txt", fileInfo[1].Name);
465 + using (Stream cabStream = File.OpenRead("testgetfiles.cab"))
466 + {
467 + fileInfo = cabEngine.GetFileInfo(new ArchiveFileStreamContext("testgetfiles.cab"), null);
468 + }
469 + Assert.IsNotNull(fileInfo);
470 + Assert.AreEqual<int>(2, fileInfo.Count);
471 + Assert.AreEqual<string>("testgetfiles0.txt", fileInfo[0].Name);
472 + Assert.AreEqual<string>("testgetfiles1.txt", fileInfo[1].Name);
473 + }
474 +
475 + fileInfo = this.RunCabinetPackUnpack(15, 20 * 1024, 1 * 1024, 130 * 1024);
476 + Assert.IsNotNull(fileInfo);
477 + Assert.AreEqual<int>(15, fileInfo.Count);
478 + for (int i = 0; i < fileInfo.Count; i++)
479 + {
480 + Assert.IsNull(fileInfo[i].Archive);
481 + Assert.AreEqual<string>(TEST_FILENAME_PREFIX + i + ".txt", fileInfo[i].Name);
482 + Assert.IsTrue(DateTime.Now - fileInfo[i].LastWriteTime < new TimeSpan(0, 1, 0));
483 + }
484 + }
485 +
486 + [TestMethod]
487 + public void CabExtractorExtract()
488 + {
489 + int txtSize = 40960;
490 + CabInfo cabInfo = new CabInfo("test.cab");
491 + CompressionTestUtil.GenerateRandomFile("test0.txt", 0, txtSize);
492 + CompressionTestUtil.GenerateRandomFile("test1.txt", 1, txtSize);
493 + cabInfo.PackFiles(null, new string[] { "test0.txt", "test1.txt" }, null);
494 + using (CabEngine cabEngine = new CabEngine())
495 + {
496 + using (Stream cabStream = File.OpenRead("test.cab"))
497 + {
498 + using (Stream exStream = cabEngine.Unpack(cabStream, "test0.txt"))
499 + {
500 + string str = new StreamReader(exStream).ReadToEnd();
501 + string expected = new StreamReader("test0.txt").ReadToEnd();
502 + Assert.AreEqual<string>(expected, str);
503 + }
504 + cabStream.Seek(0, SeekOrigin.Begin);
505 + using (Stream exStream = cabEngine.Unpack(cabStream, "test1.txt"))
506 + {
507 + string str = new StreamReader(exStream).ReadToEnd();
508 + string expected = new StreamReader("test1.txt").ReadToEnd();
509 + Assert.AreEqual<string>(expected, str);
510 + }
511 + }
512 + using (Stream txtStream = File.OpenRead("test0.txt"))
513 + {
514 + Exception caughtEx = null;
515 + try
516 + {
517 + cabEngine.Unpack(txtStream, "test0.txt");
518 + }
519 + catch (Exception ex) { caughtEx = ex; }
520 + Assert.IsInstanceOfType(caughtEx, typeof(CabException));
521 + Assert.AreEqual<int>(2, ((CabException) caughtEx).Error);
522 + Assert.AreEqual<int>(0, ((CabException) caughtEx).ErrorCode);
523 + Assert.AreEqual<string>("Cabinet file does not have the correct format.", caughtEx.Message);
524 + }
525 + }
526 + }
527 +
528 + [TestMethod]
529 + public void CabBadUnpackStreamContexts()
530 + {
531 + int txtSize = 40960;
532 + CabInfo cabInfo = new CabInfo("test2.cab");
533 + CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, txtSize);
534 + CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, txtSize);
535 + cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null);
536 +
537 + using (CabEngine cabEngine = new CabEngine())
538 + {
539 + CompressionTestUtil.TestBadUnpackStreamContexts(cabEngine, "test2.cab");
540 + }
541 + }
542 +
543 + [TestMethod]
544 + public void CabinetExtractUpdate()
545 + {
546 + int fileCount = 5, fileSize = 2048;
547 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
548 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
549 + Directory.CreateDirectory(dirA);
550 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
551 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
552 + Directory.CreateDirectory(dirB);
553 +
554 + string[] files = new string[fileCount];
555 + for (int iFile = 0; iFile < fileCount; iFile++)
556 + {
557 + files[iFile] = "€" + iFile + ".txt";
558 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
559 + }
560 +
561 + CabInfo cabInfo = new CabInfo("testupdate.cab");
562 + cabInfo.Pack(dirA);
563 + cabInfo.Unpack(dirB);
564 +
565 + DateTime originalTime = File.GetLastWriteTime(Path.Combine(dirA, "€1.txt"));
566 + DateTime pastTime = originalTime - new TimeSpan(0, 5, 0);
567 + DateTime futureTime = originalTime + new TimeSpan(0, 5, 0);
568 +
569 + using (CabEngine cabEngine = new CabEngine())
570 + {
571 + string cabName = "testupdate.cab";
572 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(cabName, dirB, null);
573 + streamContext.ExtractOnlyNewerFiles = true;
574 +
575 + Assert.AreEqual<bool>(true, streamContext.ExtractOnlyNewerFiles);
576 + Assert.IsNotNull(streamContext.ArchiveFiles);
577 + Assert.AreEqual<int>(1, streamContext.ArchiveFiles.Count);
578 + Assert.AreEqual<string>(cabName, streamContext.ArchiveFiles[0]);
579 + Assert.AreEqual<string>(dirB, streamContext.Directory);
580 +
581 + File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), futureTime);
582 + cabEngine.Unpack(streamContext, null);
583 + Assert.IsTrue(File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime > new TimeSpan(0, 4, 55));
584 +
585 + File.SetLastWriteTime(Path.Combine(dirB, "€1.txt"), pastTime);
586 + File.SetLastWriteTime(Path.Combine(dirB, "€2.txt"), pastTime);
587 + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.ReadOnly);
588 + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.Hidden);
589 + File.SetAttributes(Path.Combine(dirB, "€2.txt"), FileAttributes.System);
590 +
591 + cabEngine.Unpack(streamContext, null);
592 + Assert.IsTrue((File.GetLastWriteTime(Path.Combine(dirB, "€1.txt")) - originalTime).Duration() < new TimeSpan(0, 0, 5));
593 +
594 + // Just test the rest of the streamContext properties here.
595 + IDictionary<string, string> testMap = new Dictionary<string, string>();
596 + streamContext = new ArchiveFileStreamContext(cabName, dirB, testMap);
597 + Assert.AreSame(testMap, streamContext.Files);
598 +
599 + Assert.IsFalse(streamContext.EnableOffsetOpen);
600 + streamContext.EnableOffsetOpen = true;
601 + Assert.IsTrue(streamContext.EnableOffsetOpen);
602 + streamContext = new ArchiveFileStreamContext(cabName, ".", testMap);
603 + Assert.AreEqual<string>(".", streamContext.Directory);
604 + string[] testArchiveFiles = new string[] { cabName };
605 + streamContext = new ArchiveFileStreamContext(testArchiveFiles, ".", testMap);
606 + Assert.AreSame(testArchiveFiles, streamContext.ArchiveFiles);
607 + }
608 + }
609 +
610 + [TestMethod]
611 + public void CabinetOffset()
612 + {
613 + int txtSize = 10240;
614 + CompressionTestUtil.GenerateRandomFile("test.txt", 0, txtSize);
615 + CompressionTestUtil.GenerateRandomFile("base.txt", 1, 2 * txtSize + 4);
616 +
617 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("base.txt", null, null);
618 + streamContext.EnableOffsetOpen = true;
619 +
620 + using (CabEngine cabEngine = new CabEngine())
621 + {
622 + cabEngine.Pack(streamContext, new string[] { "test.txt" });
623 + }
624 +
625 + Assert.IsTrue(new FileInfo("base.txt").Length > 2 * txtSize + 4);
626 +
627 + string saveText;
628 + using (Stream txtStream = File.OpenRead("test.txt"))
629 + {
630 + saveText = new StreamReader(txtStream).ReadToEnd();
631 + }
632 + File.Delete("test.txt");
633 +
634 + using (CabEngine cex = new CabEngine())
635 + {
636 + cex.Unpack(streamContext, null);
637 + }
638 + string testText;
639 + using (Stream txtStream = File.OpenRead("test.txt"))
640 + {
641 + testText = new StreamReader(txtStream).ReadToEnd();
642 + }
643 + Assert.AreEqual<string>(saveText, testText);
644 + }
645 +
646 + [TestMethod]
647 + public void CabinetUtfPaths()
648 + {
649 + string[] files = new string[]
650 + {
651 + "어그리먼트送信ポート1ßà_Agreement.txt",
652 + "콘토소ßà_MyProfile.txt",
653 + "파트너1ßà_PartnerProfile.txt",
654 + };
655 +
656 + string dirA = "utf8-A";
657 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
658 + Directory.CreateDirectory(dirA);
659 + string dirB = "utf8-B";
660 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
661 + Directory.CreateDirectory(dirB);
662 +
663 + int txtSize = 1024;
664 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[0]), 0, txtSize);
665 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[1]), 1, txtSize);
666 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[2]), 2, txtSize);
667 +
668 + ArchiveFileStreamContext streamContextA = new ArchiveFileStreamContext("utf8.cab", dirA, null);
669 + using (CabEngine cabEngine = new CabEngine())
670 + {
671 + cabEngine.Pack(streamContextA, files);
672 + }
673 +
674 + ArchiveFileStreamContext streamContextB = new ArchiveFileStreamContext("utf8.cab", dirB, null);
675 + using (CabEngine cex = new CabEngine())
676 + {
677 + cex.Unpack(streamContextB, null);
678 + }
679 +
680 + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
681 + Assert.IsTrue(directoryMatch,
682 + "Testing whether cabinet output directory matches input directory.");
683 + }
684 +
685 + [TestMethod]
686 + //[Ignore] // Requires clean environment.
687 + public void CabInfoProperties()
688 + {
689 + Exception caughtEx;
690 + CabInfo cabInfo = new CabInfo("test.cab");
691 + int txtSize = 10240;
692 + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
693 + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
694 + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
695 +
696 + Assert.AreEqual<string>(new FileInfo("test.cab").Directory.FullName, cabInfo.Directory.FullName, "CabInfo.FullName");
697 + Assert.AreEqual<string>(new FileInfo("test.cab").DirectoryName, cabInfo.DirectoryName, "CabInfo.DirectoryName");
698 + Assert.AreEqual<long>(new FileInfo("test.cab").Length, cabInfo.Length, "CabInfo.Length");
699 + Assert.AreEqual<string>("test.cab", cabInfo.Name, "CabInfo.Name");
700 + Assert.AreEqual<string>(new FileInfo("test.cab").FullName, cabInfo.ToString(), "CabInfo.ToString()");
701 + cabInfo.CopyTo("test3.cab");
702 + caughtEx = null;
703 + try
704 + {
705 + cabInfo.CopyTo("test3.cab");
706 + }
707 + catch (Exception ex) { caughtEx = ex; }
708 + Assert.IsInstanceOfType(caughtEx, typeof(IOException), "CabInfo.CopyTo() caught exception: " + caughtEx);
709 + cabInfo.CopyTo("test3.cab", true);
710 + cabInfo.MoveTo("test4.cab");
711 + Assert.AreEqual<string>("test4.cab", cabInfo.Name);
712 + Assert.IsTrue(cabInfo.Exists, "CabInfo.Exists()");
713 + Assert.IsTrue(cabInfo.IsValid(), "CabInfo.IsValid");
714 + cabInfo.Delete();
715 + Assert.IsFalse(cabInfo.Exists, "!CabInfo.Exists()");
716 + }
717 +
718 + [TestMethod]
719 + //[Ignore] // Requires clean environment.
720 + public void CabInfoNullParams()
721 + {
722 + int fileCount = 10, fileSize = 1024;
723 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
724 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
725 + Directory.CreateDirectory(dirA);
726 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
727 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
728 + Directory.CreateDirectory(dirB);
729 +
730 + string[] files = new string[fileCount];
731 + for (int iFile = 0; iFile < fileCount; iFile++)
732 + {
733 + files[iFile] = "cabinfo-" + iFile + ".txt";
734 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
735 + }
736 +
737 + CabInfo cabInfo = new CabInfo("testnull.cab");
738 +
739 + CompressionTestUtil.TestArchiveInfoNullParams(cabInfo, dirA, dirB, files);
740 + }
741 +
742 + [TestMethod]
743 + public void CabInfoGetFiles()
744 + {
745 + IList<CabFileInfo> fileInfo;
746 + CabInfo cabInfo = new CabInfo("test.cab");
747 + int txtSize = 10240;
748 + CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize);
749 + CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize);
750 + cabInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt" }, null);
751 +
752 + fileInfo = cabInfo.GetFiles();
753 + Assert.IsNotNull(fileInfo);
754 + Assert.AreEqual<int>(2, fileInfo.Count);
755 + Assert.AreEqual<string>("testinfo0.txt", fileInfo[0].Name);
756 + Assert.AreEqual<string>("testinfo1.txt", fileInfo[1].Name);
757 +
758 + fileInfo = cabInfo.GetFiles("*.txt");
759 + Assert.IsNotNull(fileInfo);
760 + Assert.AreEqual<int>(2, fileInfo.Count);
761 + Assert.AreEqual<string>("testinfo0.txt", fileInfo[0].Name);
762 + Assert.AreEqual<string>("testinfo1.txt", fileInfo[1].Name);
763 +
764 + fileInfo = cabInfo.GetFiles("testinfo1.txt");
765 + Assert.IsNotNull(fileInfo);
766 + Assert.AreEqual<int>(1, fileInfo.Count);
767 + Assert.AreEqual<string>("testinfo1.txt", fileInfo[0].Name);
768 + }
769 +
770 + [TestMethod]
771 + public void CabInfoCompressExtract()
772 + {
773 + int fileCount = 10, fileSize = 1024;
774 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
775 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
776 + Directory.CreateDirectory(dirA);
777 + Directory.CreateDirectory(Path.Combine(dirA, "sub"));
778 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
779 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
780 + Directory.CreateDirectory(dirB);
781 +
782 + string[] files = new string[fileCount];
783 + for (int iFile = 0; iFile < fileCount; iFile++)
784 + {
785 + files[iFile] = "€" + iFile + ".txt";
786 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
787 + }
788 + CompressionTestUtil.GenerateRandomFile(Path.Combine(Path.Combine(dirA, "sub"), "€-.txt"), fileCount + 1, fileSize);
789 +
790 + CabInfo cabInfo = new CabInfo("test.cab");
791 + cabInfo.Pack(dirA);
792 + cabInfo.Unpack(dirB);
793 + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
794 + Assert.IsFalse(directoryMatch,
795 + "Testing whether cabinet output directory matches input directory.");
796 + Directory.Delete(dirB, true);
797 + Directory.CreateDirectory(dirB);
798 + cabInfo.Pack(dirA, true, CompressionLevel.Normal, null);
799 + cabInfo.Unpack(dirB);
800 + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
801 + Assert.IsTrue(directoryMatch,
802 + "Testing whether cabinet output directory matches input directory.");
803 + Directory.Delete(dirB, true);
804 + Directory.Delete(Path.Combine(dirA, "sub"), true);
805 + Directory.CreateDirectory(dirB);
806 + cabInfo.Delete();
807 +
808 + cabInfo.PackFiles(dirA, files, null);
809 + cabInfo.UnpackFiles(files, dirB, null);
810 + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
811 + Assert.IsTrue(directoryMatch,
812 + "Testing whether cabinet output directory matches input directory.");
813 + Directory.Delete(dirB, true);
814 + Directory.CreateDirectory(dirB);
815 + cabInfo.Delete();
816 +
817 + IDictionary<string, string> testMap = new Dictionary<string, string>(files.Length);
818 + for (int iFile = 0; iFile < fileCount; iFile++)
819 + {
820 + testMap[files[iFile] + ".key"] = files[iFile];
821 + }
822 + cabInfo.PackFileSet(dirA, testMap);
823 + cabInfo.UnpackFileSet(testMap, dirB);
824 + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
825 + Assert.IsTrue(directoryMatch,
826 + "Testing whether cabinet output directory matches input directory.");
827 + Directory.Delete(dirB, true);
828 + Directory.CreateDirectory(dirB);
829 +
830 + testMap.Remove(files[1] + ".key");
831 + cabInfo.UnpackFileSet(testMap, dirB);
832 + directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
833 + Assert.IsFalse(directoryMatch,
834 + "Testing whether cabinet output directory matches input directory.");
835 + Directory.Delete(dirB, true);
836 + Directory.CreateDirectory(dirB);
837 + cabInfo.Delete();
838 +
839 + cabInfo.PackFiles(dirA, files, null);
840 + cabInfo.UnpackFile("€2.txt", Path.Combine(dirB, "test.txt"));
841 + Assert.IsTrue(File.Exists(Path.Combine(dirB, "test.txt")));
842 + Assert.AreEqual<int>(1, Directory.GetFiles(dirB).Length);
843 + }
844 +
845 + [TestMethod]
846 + //[Ignore] // Requires clean environment.
847 + public void CabFileInfoProperties()
848 + {
849 + CabInfo cabInfo = new CabInfo("test.cab");
850 + int txtSize = 10240;
851 + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
852 + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
853 + File.SetAttributes("test01.txt", FileAttributes.ReadOnly | FileAttributes.Archive);
854 + DateTime testTime = File.GetLastWriteTime("test01.txt");
855 + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
856 + File.SetAttributes("test01.txt", FileAttributes.Archive);
857 +
858 + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
859 + Assert.AreEqual(cabInfo.FullName, cfi.CabinetName);
860 + Assert.AreEqual<int>(0, ((CabFileInfo) cfi).CabinetFolderNumber);
861 + Assert.AreEqual<string>(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.FullName);
862 + cfi = new CabFileInfo(cabInfo, "test01.txt");
863 + Assert.IsTrue(cfi.Exists);
864 + cfi = new CabFileInfo(cabInfo, "test01.txt");
865 + Assert.AreEqual<long>(txtSize, cfi.Length);
866 + cfi = new CabFileInfo(cabInfo, "test00.txt");
867 + Assert.AreEqual<FileAttributes>(FileAttributes.Archive, cfi.Attributes);
868 + cfi = new CabFileInfo(cabInfo, "test01.txt");
869 + Assert.AreEqual<FileAttributes>(FileAttributes.ReadOnly | FileAttributes.Archive, cfi.Attributes);
870 + cfi = new CabFileInfo(cabInfo, "test01.txt");
871 + Assert.IsTrue((testTime - cfi.LastWriteTime).Duration() < new TimeSpan(0, 0, 5));
872 + Assert.AreEqual<string>(Path.Combine(cabInfo.FullName, "test01.txt"), cfi.ToString());
873 + cfi.CopyTo("testcopy.txt");
874 + Assert.IsTrue(File.Exists("testCopy.txt"));
875 + Assert.AreEqual<long>(cfi.Length, new FileInfo("testCopy.txt").Length);
876 +
877 + Exception caughtEx = null;
878 + try
879 + {
880 + cfi.CopyTo("testcopy.txt", false);
881 + }
882 + catch (Exception ex) { caughtEx = ex; }
883 + Assert.IsInstanceOfType(caughtEx, typeof(IOException));
884 + }
885 +
886 + [TestMethod]
887 + public void CabFileInfoOpenText()
888 + {
889 + CabInfo cabInfo = new CabInfo("test.cab");
890 + int txtSize = 10240;
891 + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
892 + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
893 +
894 + string expectedText = File.ReadAllText("test01.txt");
895 +
896 + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
897 +
898 + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
899 + using (StreamReader cabFileReader = cfi.OpenText())
900 + {
901 + string text = cabFileReader.ReadToEnd();
902 + Assert.AreEqual(expectedText, text);
903 +
904 + // Check the assumption that the cab can't be deleted while a stream is open.
905 + Exception caughtEx = null;
906 + try
907 + {
908 + File.Delete(cabInfo.FullName);
909 + }
910 + catch (Exception ex)
911 + {
912 + caughtEx = ex;
913 + }
914 +
915 + Assert.IsInstanceOfType(caughtEx, typeof(IOException));
916 + }
917 +
918 + // Ensure all streams are closed after disposing of the StreamReader returned by OpenText.
919 + File.Delete(cabInfo.FullName);
920 + }
921 +
922 + [TestMethod]
923 + public void CabFileInfoNullParams()
924 + {
925 + Exception caughtEx;
926 + CabInfo cabInfo = new CabInfo("test.cab");
927 + int txtSize = 10240;
928 + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
929 + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
930 + cabInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
931 + CabFileInfo cfi = new CabFileInfo(cabInfo, "test01.txt");
932 +
933 + caughtEx = null;
934 + try
935 + {
936 + new CabFileInfo(null, "test00.txt");
937 + }
938 + catch (Exception ex) { caughtEx = ex; }
939 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
940 + caughtEx = null;
941 + try
942 + {
943 + new CabFileInfo(cabInfo, null);
944 + }
945 + catch (Exception ex) { caughtEx = ex; }
946 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
947 + caughtEx = null;
948 + try
949 + {
950 + cfi.CopyTo(null);
951 + }
952 + catch (Exception ex) { caughtEx = ex; }
953 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
954 + }
955 +
956 + [TestMethod]
957 + public void CabInfoSerialization()
958 + {
959 + CabInfo cabInfo = new CabInfo("testser.cab");
960 + int txtSize = 10240;
961 + CompressionTestUtil.GenerateRandomFile("testser00.txt", 0, txtSize);
962 + CompressionTestUtil.GenerateRandomFile("testser01.txt", 1, txtSize);
963 + cabInfo.PackFiles(null, new string[] { "testser00.txt", "testser01.txt" }, null);
964 + ArchiveFileInfo cfi = cabInfo.GetFiles()[1];
965 +
966 + MemoryStream memStream = new MemoryStream();
967 +
968 + BinaryFormatter formatter = new BinaryFormatter();
969 +
970 + memStream.Seek(0, SeekOrigin.Begin);
971 + formatter.Serialize(memStream, cabInfo);
972 + memStream.Seek(0, SeekOrigin.Begin);
973 + CabInfo cabInfo2 = (CabInfo) formatter.Deserialize(memStream);
974 + Assert.AreEqual<string>(cabInfo.FullName, cabInfo2.FullName);
975 +
976 + memStream.Seek(0, SeekOrigin.Begin);
977 + formatter.Serialize(memStream, cfi);
978 + memStream.Seek(0, SeekOrigin.Begin);
979 + CabFileInfo cfi2 = (CabFileInfo) formatter.Deserialize(memStream);
980 + Assert.AreEqual<string>(cfi.FullName, cfi2.FullName);
981 + Assert.AreEqual<long>(cfi.Length, cfi2.Length);
982 +
983 + CabException cabEx = new CabException();
984 + memStream.Seek(0, SeekOrigin.Begin);
985 + formatter.Serialize(memStream, cabEx);
986 + memStream.Seek(0, SeekOrigin.Begin);
987 + formatter.Deserialize(memStream);
988 +
989 + cabEx = new CabException("Test exception.", null);
990 + Assert.AreEqual<string>("Test exception.", cabEx.Message);
991 + }
992 +
993 + [TestMethod]
994 + public void CabFileStreamContextNullParams()
995 + {
996 + ArchiveFileStreamContext streamContext = null;
997 + Exception caughtEx = null;
998 + try
999 + {
1000 + streamContext = new ArchiveFileStreamContext(null);
1001 + }
1002 + catch (Exception ex) { caughtEx = ex; }
1003 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing null to constructor.");
1004 + caughtEx = null;
1005 + try
1006 + {
1007 + streamContext = new ArchiveFileStreamContext(new string[] { }, "testDir", new Dictionary<string, string>());
1008 + }
1009 + catch (Exception ex) { caughtEx = ex; }
1010 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Passing 0-length array to constructor.");
1011 + caughtEx = null;
1012 + try
1013 + {
1014 + streamContext = new ArchiveFileStreamContext(new string[] { "test.cab" }, null, null);
1015 + }
1016 + catch (Exception ex) { caughtEx = ex; }
1017 + Assert.IsNull(caughtEx);
1018 + }
1019 +
1020 + [TestMethod]
1021 + public void CabinetTruncateOnCreate()
1022 + {
1023 + CabInfo cabInfo = new CabInfo("testtruncate.cab");
1024 + int txtSize = 20240;
1025 + CompressionTestUtil.GenerateRandomFile("testtruncate0.txt", 0, txtSize);
1026 + CompressionTestUtil.GenerateRandomFile("testtruncate1.txt", 1, txtSize);
1027 + cabInfo.PackFiles(null, new string[] { "testtruncate0.txt", "testtruncate1.txt" }, null);
1028 +
1029 + long size1 = cabInfo.Length;
1030 +
1031 + txtSize /= 5;
1032 + CompressionTestUtil.GenerateRandomFile("testtruncate2.txt", 2, txtSize);
1033 + CompressionTestUtil.GenerateRandomFile("testtruncate3.txt", 3, txtSize);
1034 + cabInfo.PackFiles(null, new string[] { "testtruncate2.txt", "testtruncate3.txt" }, null);
1035 +
1036 + // The newly created cab file should be smaller than before.
1037 + Assert.AreNotEqual<long>(size1, cabInfo.Length, "Checking that cabinet file got truncated when creating a smaller cab in-place.");
1038 + }
1039 +
1040 + [TestMethod]
1041 + public void CabTruncatedArchive()
1042 + {
1043 + CabInfo cabInfo = new CabInfo("test-t.cab");
1044 + CompressionTestUtil.GenerateRandomFile("cabtest-0.txt", 0, 5);
1045 + CompressionTestUtil.GenerateRandomFile("cabtest-1.txt", 1, 5);
1046 + cabInfo.PackFiles(null, new string[] { "cabtest-0.txt", "cabtest-1.txt" }, null);
1047 +
1048 + CompressionTestUtil.TestTruncatedArchive(cabInfo, typeof(CabException));
1049 + }
1050 + private const string TEST_FILENAME_PREFIX = "\x20AC";
1051 +
1052 + private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize)
1053 + {
1054 + return RunCabinetPackUnpack(fileCount, fileSize, 0, 0);
1055 + }
1056 + private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize,
1057 + long maxFolderSize, long maxArchiveSize)
1058 + {
1059 + return this.RunCabinetPackUnpack(fileCount, fileSize, maxFolderSize, maxArchiveSize, CompressionLevel.Normal);
1060 + }
1061 + private IList<ArchiveFileInfo> RunCabinetPackUnpack(int fileCount, long fileSize,
1062 + long maxFolderSize, long maxArchiveSize, CompressionLevel compLevel)
1063 + {
1064 + Console.WriteLine("Creating cabinet with {0} files of size {1}",
1065 + fileCount, fileSize);
1066 + Console.WriteLine("MaxFolderSize={0}, MaxArchiveSize={1}, CompressionLevel={2}",
1067 + maxFolderSize, maxArchiveSize, compLevel);
1068 +
1069 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
1070 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
1071 + Directory.CreateDirectory(dirA);
1072 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
1073 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
1074 + Directory.CreateDirectory(dirB);
1075 +
1076 + string[] files = new string[fileCount];
1077 + for (int iFile = 0; iFile < fileCount; iFile++)
1078 + {
1079 + files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt";
1080 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
1081 + }
1082 +
1083 + string[] archiveNames = new string[100];
1084 + for (int i = 0; i < archiveNames.Length; i++)
1085 + {
1086 + archiveNames[i] = String.Format("{0}-{1}{2}{3}.cab", fileCount, fileSize,
1087 + (i == 0 ? "" : "-"), (i == 0 ? "" : i.ToString()));
1088 + }
1089 +
1090 + string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize);
1091 + CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile);
1092 +
1093 + IList<ArchiveFileInfo> fileInfo;
1094 + using (CabEngine cabEngine = new CabEngine())
1095 + {
1096 + cabEngine.CompressionLevel = compLevel;
1097 +
1098 + File.AppendAllText(progressTextFile,
1099 + "\r\n\r\n====================================================\r\nCREATE\r\n\r\n");
1100 + cabEngine.Progress += testUtil.PrintArchiveProgress;
1101 +
1102 + OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null);
1103 + if (maxFolderSize == 1)
1104 + {
1105 + streamContext.OptionHandler =
1106 + delegate(string optionName, object[] parameters)
1107 + {
1108 + if (optionName == "nextFolder") return true;
1109 + return null;
1110 + };
1111 + }
1112 + else if (maxFolderSize > 1)
1113 + {
1114 + streamContext.OptionHandler =
1115 + delegate(string optionName, object[] parameters)
1116 + {
1117 + if (optionName == "maxFolderSize") return maxFolderSize;
1118 + return null;
1119 + };
1120 + }
1121 + cabEngine.Pack(streamContext, files, maxArchiveSize);
1122 +
1123 + IList<string> createdArchiveNames = new List<string>(archiveNames.Length);
1124 + for (int i = 0; i < archiveNames.Length; i++)
1125 + {
1126 + if (File.Exists(archiveNames[i]))
1127 + {
1128 + createdArchiveNames.Add(archiveNames[i]);
1129 + }
1130 + else
1131 + {
1132 + break;
1133 + }
1134 + }
1135 +
1136 + Console.WriteLine("Listing cabinet with {0} files of size {1}",
1137 + fileCount, fileSize);
1138 + File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n");
1139 + fileInfo = cabEngine.GetFileInfo(
1140 + new ArchiveFileStreamContext(createdArchiveNames, null, null), null);
1141 +
1142 + Assert.AreEqual<int>(fileCount, fileInfo.Count);
1143 + if (fileCount > 0)
1144 + {
1145 + int folders = ((CabFileInfo) fileInfo[fileInfo.Count - 1]).CabinetFolderNumber + 1;
1146 + if (maxFolderSize == 1)
1147 + {
1148 + Assert.AreEqual<int>(fileCount, folders);
1149 + }
1150 + }
1151 +
1152 + Console.WriteLine("Extracting cabinet with {0} files of size {1}",
1153 + fileCount, fileSize);
1154 + File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n");
1155 + cabEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null);
1156 + }
1157 +
1158 + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
1159 + Assert.IsTrue(directoryMatch,
1160 + "Testing whether cabinet output directory matches input directory.");
1161 +
1162 + return fileInfo;
1163 + }
1164 + }
1165 +}
src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj new
+32
@@ -0,0 +1,32 @@
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" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 + <PropertyGroup>
6 + <ProjectGuid>{4544158C-2D63-4146-85FF-62169280144E}</ProjectGuid>
7 + <OutputType>Library</OutputType>
8 + <RootNamespace>WixToolsetTests.Dtf.Cab</RootNamespace>
9 + <AssemblyName>WixToolsetTests.Dtf.Cab</AssemblyName>
10 + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 + <CreateDocumentation>false</CreateDocumentation>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="CabTest.cs" />
16 + </ItemGroup>
17 +
18 + <ItemGroup>
19 + <Reference Include="System" />
20 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 + <Reference Include="System.Data" />
22 + <Reference Include="System.Xml" />
23 + </ItemGroup>
24 +
25 + <ItemGroup>
26 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
27 + <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
28 + <ProjectReference Include="..\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj" />
29 + </ItemGroup>
30 +
31 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
32 +</Project>
src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj new
+30
@@ -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 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 + <PropertyGroup>
6 + <ProjectGuid>{328799BB-7B03-4B28-8180-4132211FD07D}</ProjectGuid>
7 + <OutputType>Library</OutputType>
8 + <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 + <AssemblyName>WixToolsetTests.Dtf.Zip</AssemblyName>
10 + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 + <CreateDocumentation>false</CreateDocumentation>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="ZipTest.cs" />
16 + </ItemGroup>
17 +
18 + <ItemGroup>
19 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
20 + <Reference Include="System" />
21 + </ItemGroup>
22 +
23 + <ItemGroup>
24 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
25 + <ProjectReference Include="..\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj" />
26 + <ProjectReference Include="..\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj" />
27 + </ItemGroup>
28 +
29 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
30 +</Project>
src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs new
+518
@@ -0,0 +1,518 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using Microsoft.VisualStudio.TestTools.UnitTesting;
10 + using WixToolset.Dtf.Compression;
11 + using WixToolset.Dtf.Compression.Zip;
12 +
13 + [TestClass]
14 + public class ZipTest
15 + {
16 + public ZipTest()
17 + {
18 + }
19 +
20 + [TestInitialize]
21 + public void Initialize()
22 + {
23 + }
24 +
25 + [TestCleanup]
26 + public void Cleanup()
27 + {
28 + }
29 +
30 + [TestMethod]
31 + public void ZipFileCounts()
32 + {
33 + this.RunZipPackUnpack(0, 10, 0);
34 + this.RunZipPackUnpack(0, 100000, 0);
35 + this.RunZipPackUnpack(1, 10, 0);
36 + this.RunZipPackUnpack(100, 10, 0);
37 + }
38 +
39 + [TestMethod]
40 + [Ignore] // Takes too long to run regularly.
41 + public void ZipExtremeFileCounts()
42 + {
43 + this.RunZipPackUnpack(66000, 10, 0);
44 + }
45 +
46 + [TestMethod]
47 + public void ZipFileSizes()
48 + {
49 + this.RunZipPackUnpack(1, 0, 0);
50 + for (int n = 1; n <= 33; n++)
51 + {
52 + this.RunZipPackUnpack(1, n, 0);
53 + }
54 + this.RunZipPackUnpack(1, 100 * 1024, 0);
55 + this.RunZipPackUnpack(1, 10 * 1024 * 1024, 0);
56 + }
57 +
58 + [Timeout(36000000), TestMethod]
59 + [Ignore] // Takes too long to run regularly.
60 + public void ZipExtremeFileSizes()
61 + {
62 + //this.RunZipPackUnpack(10, 512L * 1024 * 1024, 0); // 5GB
63 + this.RunZipPackUnpack(1, 5L * 1024 * 1024 * 1024, 0, CompressionLevel.None); // 5GB
64 + }
65 +
66 + [TestMethod]
67 + public void ZipArchiveCounts()
68 + {
69 + IList<ArchiveFileInfo> fileInfo;
70 + fileInfo = this.RunZipPackUnpack(10, 100 * 1024, 400 * 1024, CompressionLevel.None);
71 + Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
72 + "Testing whether archive spans the correct # of zip files.");
73 +
74 + fileInfo = this.RunZipPackUnpack(2, 90 * 1024, 40 * 1024, CompressionLevel.None);
75 + Assert.AreEqual<int>(2, fileInfo[fileInfo.Count - 1].ArchiveNumber,
76 + "Testing whether archive spans the correct # of zip files.");
77 + }
78 +
79 + [TestMethod]
80 + public void ZipProgress()
81 + {
82 + CompressionTestUtil.ExpectedProgress = new List<int[]>(new int[][] {
83 + // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives
84 + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 1 },
85 + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 1 },
86 + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 1 },
87 + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 1 },
88 + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 1 },
89 + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 1 },
90 + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 1 },
91 + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 1 },
92 + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 1 },
93 + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 1 },
94 + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 1 },
95 + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 1 },
96 + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 1 },
97 + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 1 },
98 + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 1 },
99 + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 2 },
100 + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 2 },
101 + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 2 },
102 + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 2 },
103 + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 2 },
104 + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 2 },
105 + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 2 },
106 + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 2 },
107 + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 2 },
108 + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 2 },
109 + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 2 },
110 + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 2 },
111 + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 2 },
112 + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 2 },
113 + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 },
114 + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 },
115 + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 },
116 + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 },
117 + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 },
118 + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 },
119 + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 },
120 + // StatusType, CurFile,TotalFiles,CurFolder,CurArchive,TotalArchives
121 + new int[] { (int) ArchiveProgressType.StartArchive, 0, 15, 0, 0, 3 },
122 + new int[] { (int) ArchiveProgressType.StartFile, 0, 15, 0, 0, 3 },
123 + new int[] { (int) ArchiveProgressType.FinishFile, 0, 15, 0, 0, 3 },
124 + new int[] { (int) ArchiveProgressType.StartFile, 1, 15, 0, 0, 3 },
125 + new int[] { (int) ArchiveProgressType.FinishFile, 1, 15, 0, 0, 3 },
126 + new int[] { (int) ArchiveProgressType.StartFile, 2, 15, 0, 0, 3 },
127 + new int[] { (int) ArchiveProgressType.FinishFile, 2, 15, 0, 0, 3 },
128 + new int[] { (int) ArchiveProgressType.StartFile, 3, 15, 0, 0, 3 },
129 + new int[] { (int) ArchiveProgressType.FinishFile, 3, 15, 0, 0, 3 },
130 + new int[] { (int) ArchiveProgressType.StartFile, 4, 15, 0, 0, 3 },
131 + new int[] { (int) ArchiveProgressType.FinishFile, 4, 15, 0, 0, 3 },
132 + new int[] { (int) ArchiveProgressType.StartFile, 5, 15, 0, 0, 3 },
133 + new int[] { (int) ArchiveProgressType.FinishFile, 5, 15, 0, 0, 3 },
134 + new int[] { (int) ArchiveProgressType.StartFile, 6, 15, 0, 0, 3 },
135 + new int[] { (int) ArchiveProgressType.FinishArchive, 6, 15, 0, 0, 3 },
136 + new int[] { (int) ArchiveProgressType.StartArchive, 6, 15, 0, 1, 3 },
137 + new int[] { (int) ArchiveProgressType.FinishFile, 6, 15, 0, 1, 3 },
138 + new int[] { (int) ArchiveProgressType.StartFile, 7, 15, 0, 1, 3 },
139 + new int[] { (int) ArchiveProgressType.FinishFile, 7, 15, 0, 1, 3 },
140 + new int[] { (int) ArchiveProgressType.StartFile, 8, 15, 0, 1, 3 },
141 + new int[] { (int) ArchiveProgressType.FinishFile, 8, 15, 0, 1, 3 },
142 + new int[] { (int) ArchiveProgressType.StartFile, 9, 15, 0, 1, 3 },
143 + new int[] { (int) ArchiveProgressType.FinishFile, 9, 15, 0, 1, 3 },
144 + new int[] { (int) ArchiveProgressType.StartFile, 10, 15, 0, 1, 3 },
145 + new int[] { (int) ArchiveProgressType.FinishFile, 10, 15, 0, 1, 3 },
146 + new int[] { (int) ArchiveProgressType.StartFile, 11, 15, 0, 1, 3 },
147 + new int[] { (int) ArchiveProgressType.FinishFile, 11, 15, 0, 1, 3 },
148 + new int[] { (int) ArchiveProgressType.StartFile, 12, 15, 0, 1, 3 },
149 + new int[] { (int) ArchiveProgressType.FinishArchive, 12, 15, 0, 1, 3 },
150 + new int[] { (int) ArchiveProgressType.StartArchive, 12, 15, 0, 2, 3 },
151 + new int[] { (int) ArchiveProgressType.FinishFile, 12, 15, 0, 2, 3 },
152 + new int[] { (int) ArchiveProgressType.StartFile, 13, 15, 0, 2, 3 },
153 + new int[] { (int) ArchiveProgressType.FinishFile, 13, 15, 0, 2, 3 },
154 + new int[] { (int) ArchiveProgressType.StartFile, 14, 15, 0, 2, 3 },
155 + new int[] { (int) ArchiveProgressType.FinishFile, 14, 15, 0, 2, 3 },
156 + new int[] { (int) ArchiveProgressType.FinishArchive, 14, 15, 0, 2, 3 },
157 + });
158 + CompressionTestUtil.ExpectedProgress = null;
159 +
160 + try
161 + {
162 + this.RunZipPackUnpack(15, 20 * 1024, 130 * 1024, CompressionLevel.None);
163 + }
164 + finally
165 + {
166 + CompressionTestUtil.ExpectedProgress = null;
167 + }
168 + }
169 +
170 + [TestMethod]
171 + //[Ignore] // Requires clean environment.
172 + public void ZipArchiveSizes()
173 + {
174 + Console.WriteLine("Testing various values for the maxArchiveSize parameter.");
175 + this.RunZipPackUnpack(5, 1024, Int64.MinValue);
176 + this.RunZipPackUnpack(5, 1024, -1);
177 + this.RunZipPackUnpack(2, 10, 0);
178 +
179 + this.RunZipPackUnpack(1, 10, 1);
180 + this.RunZipPackUnpack(2, 10, 2);
181 + this.RunZipPackUnpack(2, 10, 3);
182 + this.RunZipPackUnpack(2, 10, 4);
183 + this.RunZipPackUnpack(2, 10, 5);
184 + this.RunZipPackUnpack(2, 10, 6);
185 + this.RunZipPackUnpack(2, 10, 7);
186 + this.RunZipPackUnpack(5, 10, 8);
187 + this.RunZipPackUnpack(5, 10, 9);
188 + this.RunZipPackUnpack(5, 10, 10);
189 + this.RunZipPackUnpack(5, 10, 11);
190 + this.RunZipPackUnpack(5, 10, 12);
191 +
192 + this.RunZipPackUnpack(5, 101, 255);
193 + this.RunZipPackUnpack(5, 102, 256);
194 + this.RunZipPackUnpack(5, 103, 257);
195 + this.RunZipPackUnpack(5, 24000, 32768);
196 + this.RunZipPackUnpack(5, 1024, Int64.MaxValue);
197 + }
198 +
199 + [TestMethod]
200 + public void ZipCompLevelParam()
201 + {
202 + Console.WriteLine("Testing various values for the compressionLevel parameter.");
203 + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.None);
204 + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Min);
205 + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Normal);
206 + this.RunZipPackUnpack(5, 1024, 0, CompressionLevel.Max);
207 + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.None - 1));
208 + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) ((int) CompressionLevel.Max + 1));
209 + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MinValue);
210 + this.RunZipPackUnpack(5, 1024, 0, (CompressionLevel) Int32.MaxValue);
211 + }
212 +
213 + [TestMethod]
214 + public void ZipInfoGetFiles()
215 + {
216 + IList<ZipFileInfo> fileInfos;
217 + ZipInfo zipInfo = new ZipInfo("testgetfiles.zip");
218 +
219 + int txtSize = 10240;
220 + CompressionTestUtil.GenerateRandomFile("testinfo0.txt", 0, txtSize);
221 + CompressionTestUtil.GenerateRandomFile("testinfo1.txt", 1, txtSize);
222 + CompressionTestUtil.GenerateRandomFile("testinfo2.ini", 2, txtSize);
223 + zipInfo.PackFiles(null, new string[] { "testinfo0.txt", "testinfo1.txt", "testinfo2.ini" }, null);
224 +
225 + fileInfos = zipInfo.GetFiles();
226 + Assert.IsNotNull(fileInfos);
227 + Assert.AreEqual<int>(3, fileInfos.Count);
228 + Assert.AreEqual<string>("testinfo0.txt", fileInfos[0].Name);
229 + Assert.AreEqual<string>("testinfo1.txt", fileInfos[1].Name);
230 + Assert.AreEqual<string>("testinfo2.ini", fileInfos[2].Name);
231 +
232 + fileInfos = zipInfo.GetFiles("*.txt");
233 + Assert.IsNotNull(fileInfos);
234 + Assert.AreEqual<int>(2, fileInfos.Count);
235 + Assert.AreEqual<string>("testinfo0.txt", fileInfos[0].Name);
236 + Assert.AreEqual<string>("testinfo1.txt", fileInfos[1].Name);
237 +
238 + fileInfos = zipInfo.GetFiles("testinfo1.txt");
239 + Assert.IsNotNull(fileInfos);
240 + Assert.AreEqual<int>(1, fileInfos.Count);
241 + Assert.AreEqual<string>("testinfo1.txt", fileInfos[0].Name);
242 + Assert.IsTrue(DateTime.Now - fileInfos[0].LastWriteTime < TimeSpan.FromMinutes(1),
243 + "Checking ZipFileInfo.LastWriteTime is current.");
244 + }
245 +
246 + [TestMethod]
247 + //[Ignore] // Requires clean environment.
248 + public void ZipInfoNullParams()
249 + {
250 + int fileCount = 10, fileSize = 1024;
251 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
252 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
253 + Directory.CreateDirectory(dirA);
254 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
255 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
256 + Directory.CreateDirectory(dirB);
257 +
258 + string[] files = new string[fileCount];
259 + for (int iFile = 0; iFile < fileCount; iFile++)
260 + {
261 + files[iFile] = "zipinfo-" + iFile + ".txt";
262 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
263 + }
264 +
265 + ZipInfo zipInfo = new ZipInfo("testnull.zip");
266 +
267 + CompressionTestUtil.TestArchiveInfoNullParams(zipInfo, dirA, dirB, files);
268 + }
269 +
270 + [TestMethod]
271 + public void ZipFileInfoNullParams()
272 + {
273 + Exception caughtEx;
274 + ZipInfo zipInfo = new ZipInfo("test.zip");
275 + int txtSize = 10240;
276 + CompressionTestUtil.GenerateRandomFile("test00.txt", 0, txtSize);
277 + CompressionTestUtil.GenerateRandomFile("test01.txt", 1, txtSize);
278 + zipInfo.PackFiles(null, new string[] { "test00.txt", "test01.txt" }, null);
279 + ZipFileInfo zfi = new ZipFileInfo(zipInfo, "test01.txt");
280 +
281 + caughtEx = null;
282 + try
283 + {
284 + new ZipFileInfo(null, "test00.txt");
285 + }
286 + catch (Exception ex) { caughtEx = ex; }
287 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
288 + caughtEx = null;
289 + try
290 + {
291 + new ZipFileInfo(zipInfo, null);
292 + }
293 + catch (Exception ex) { caughtEx = ex; }
294 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
295 + caughtEx = null;
296 + try
297 + {
298 + zfi.CopyTo(null);
299 + }
300 + catch (Exception ex) { caughtEx = ex; }
301 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException));
302 + }
303 +
304 + [TestMethod]
305 + public void ZipEngineNullParams()
306 + {
307 + string[] testFiles = new string[] { "test.txt" };
308 + ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext("test.zip", null, null);
309 +
310 + using (ZipEngine zipEngine = new ZipEngine())
311 + {
312 + zipEngine.CompressionLevel = CompressionLevel.None;
313 +
314 + CompressionTestUtil.TestCompressionEngineNullParams(zipEngine, streamContext, testFiles);
315 + }
316 + }
317 +
318 + [TestMethod]
319 + public void ZipBadPackStreamContexts()
320 + {
321 + string[] testFiles = new string[] { "test.txt" };
322 + CompressionTestUtil.GenerateRandomFile(testFiles[0], 0, 20000);
323 +
324 + using (ZipEngine zipEngine = new ZipEngine())
325 + {
326 + zipEngine.CompressionLevel = CompressionLevel.None;
327 +
328 + CompressionTestUtil.TestBadPackStreamContexts(zipEngine, "test.zip", testFiles);
329 + }
330 + }
331 +
332 + [TestMethod]
333 + public void ZipBadUnpackStreamContexts()
334 + {
335 + int txtSize = 40960;
336 + ZipInfo zipInfo = new ZipInfo("test2.zip");
337 + CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, txtSize);
338 + CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, txtSize);
339 + zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null);
340 +
341 + using (ZipEngine zipEngine = new ZipEngine())
342 + {
343 + CompressionTestUtil.TestBadUnpackStreamContexts(zipEngine, "test2.zip");
344 + }
345 + }
346 +
347 + [TestMethod]
348 + [Ignore] // Failed on build server, need to investigate.
349 + public void ZipTruncatedArchive()
350 + {
351 + ZipInfo zipInfo = new ZipInfo("test-t.zip");
352 + CompressionTestUtil.GenerateRandomFile("ziptest-0.txt", 0, 5);
353 + CompressionTestUtil.GenerateRandomFile("ziptest-1.txt", 1, 5);
354 + zipInfo.PackFiles(null, new string[] { "ziptest-0.txt", "ziptest-1.txt" }, null);
355 +
356 + CompressionTestUtil.TestTruncatedArchive(zipInfo, typeof(ZipException));
357 + }
358 +
359 + /*
360 + [TestMethod]
361 + public void ZipUnpack()
362 + {
363 + IList<ZipFileInfo> fileInfos;
364 + foreach (FileInfo zipFile in new DirectoryInfo("D:\\temp").GetFiles("*.zip"))
365 + {
366 + Console.WriteLine("=====================================================");
367 + Console.WriteLine(zipFile.FullName);
368 + Console.WriteLine("=====================================================");
369 + ZipInfo zipTest = new ZipInfo(zipFile.FullName);
370 + fileInfos = zipTest.GetFiles();
371 + Assert.AreNotEqual<int>(0, fileInfos.Count);
372 + foreach (ArchiveFileInfo file in fileInfos)
373 + {
374 + Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime);
375 + }
376 +
377 + Directory.CreateDirectory(Path.GetFileNameWithoutExtension(zipFile.Name));
378 + zipTest.Unpack(Path.GetFileNameWithoutExtension(zipFile.Name));
379 + }
380 + }
381 + */
382 +
383 + /*
384 + [TestMethod]
385 + public void ZipUnpackSelfExtractor()
386 + {
387 + ZipInfo zipTest = new ZipInfo(@"C:\temp\testzip.exe");
388 + IList<ZipFileInfo> fileInfos = zipTest.GetFiles();
389 + Assert.AreNotEqual<int>(0, fileInfos.Count);
390 + foreach (ArchiveFileInfo file in fileInfos)
391 + {
392 + Console.WriteLine("{0}\t{1}\t{2}", Path.Combine(file.Path, file.Name), file.Length, file.LastWriteTime);
393 + }
394 +
395 + string extractDir = Path.GetFileNameWithoutExtension(zipTest.Name);
396 + Directory.CreateDirectory(extractDir);
397 + zipTest.Unpack(extractDir);
398 + }
399 + */
400 +
401 + private const string TEST_FILENAME_PREFIX = "\x20AC";
402 +
403 + private IList<ArchiveFileInfo> RunZipPackUnpack(int fileCount, long fileSize,
404 + long maxArchiveSize)
405 + {
406 + return this.RunZipPackUnpack(fileCount, fileSize, maxArchiveSize, CompressionLevel.Normal);
407 + }
408 +
409 + private IList<ArchiveFileInfo> RunZipPackUnpack(int fileCount, long fileSize,
410 + long maxArchiveSize, CompressionLevel compLevel)
411 + {
412 + Console.WriteLine("Creating zip archive with {0} files of size {1}",
413 + fileCount, fileSize);
414 + Console.WriteLine("MaxArchiveSize={0}, CompressionLevel={1}", maxArchiveSize, compLevel);
415 +
416 + string dirA = String.Format("{0}-{1}-A", fileCount, fileSize);
417 + if (Directory.Exists(dirA)) Directory.Delete(dirA, true);
418 + Directory.CreateDirectory(dirA);
419 + string dirB = String.Format("{0}-{1}-B", fileCount, fileSize);
420 + if (Directory.Exists(dirB)) Directory.Delete(dirB, true);
421 + Directory.CreateDirectory(dirB);
422 +
423 + string[] files = new string[fileCount];
424 + for (int iFile = 0; iFile < fileCount; iFile++)
425 + {
426 + files[iFile] = TEST_FILENAME_PREFIX + iFile + ".txt";
427 + CompressionTestUtil.GenerateRandomFile(Path.Combine(dirA, files[iFile]), iFile, fileSize);
428 + }
429 +
430 + string[] archiveNames = new string[1000];
431 + for (int i = 0; i < archiveNames.Length; i++)
432 + {
433 + if (i < 100)
434 + {
435 + archiveNames[i] = String.Format(
436 + (i == 0 ? "{0}-{1}.zip" : "{0}-{1}.z{2:d02}"),
437 + fileCount, fileSize, i);
438 + }
439 + else
440 + {
441 + archiveNames[i] = String.Format(
442 + "{0}-{1}.{2:d03}", fileCount, fileSize, i);
443 + }
444 + }
445 +
446 + string progressTextFile = String.Format("progress_{0}-{1}.txt", fileCount, fileSize);
447 + CompressionTestUtil testUtil = new CompressionTestUtil(progressTextFile);
448 +
449 + IList<ArchiveFileInfo> fileInfo;
450 + using (ZipEngine zipEngine = new ZipEngine())
451 + {
452 + zipEngine.CompressionLevel = compLevel;
453 +
454 + File.AppendAllText(progressTextFile,
455 + "\r\n\r\n====================================================\r\nCREATE\r\n\r\n");
456 + zipEngine.Progress += testUtil.PrintArchiveProgress;
457 +
458 + OptionStreamContext streamContext = new OptionStreamContext(archiveNames, dirA, null);
459 + streamContext.OptionHandler =
460 + delegate(string optionName, object[] parameters)
461 + {
462 + // For testing purposes, force zip64 for only moderately large files.
463 + switch (optionName)
464 + {
465 + case "forceZip64":
466 + return fileSize > UInt16.MaxValue;
467 + default:
468 + return null;
469 + }
470 + };
471 +
472 + zipEngine.Pack(streamContext, files, maxArchiveSize);
473 +
474 + string checkArchiveName = archiveNames[0];
475 + if (File.Exists(archiveNames[1])) checkArchiveName = archiveNames[1];
476 + using (Stream archiveStream = File.OpenRead(checkArchiveName))
477 + {
478 + bool isArchive = zipEngine.IsArchive(archiveStream);
479 + Assert.IsTrue(isArchive, "Checking that created archive appears valid.");
480 + }
481 +
482 + IList<string> createdArchiveNames = new List<string>(archiveNames.Length);
483 + for (int i = 0; i < archiveNames.Length; i++)
484 + {
485 + if (File.Exists(archiveNames[i]))
486 + {
487 + createdArchiveNames.Add(archiveNames[i]);
488 + }
489 + else
490 + {
491 + break;
492 + }
493 + }
494 +
495 + Assert.AreNotEqual<int>(0, createdArchiveNames.Count);
496 +
497 + Console.WriteLine("Listing zip archive with {0} files of size {1}",
498 + fileCount, fileSize);
499 + File.AppendAllText(progressTextFile, "\r\n\r\nLIST\r\n\r\n");
500 + fileInfo = zipEngine.GetFileInfo(
501 + new ArchiveFileStreamContext(createdArchiveNames, null, null), null);
502 +
503 + Assert.AreEqual<int>(fileCount, fileInfo.Count);
504 +
505 + Console.WriteLine("Extracting zip archive with {0} files of size {1}",
506 + fileCount, fileSize);
507 + File.AppendAllText(progressTextFile, "\r\n\r\nEXTRACT\r\n\r\n");
508 + zipEngine.Unpack(new ArchiveFileStreamContext(createdArchiveNames, dirB, null), null);
509 + }
510 +
511 + bool directoryMatch = CompressionTestUtil.CompareDirectories(dirA, dirB);
512 + Assert.IsTrue(directoryMatch,
513 + "Testing whether zip output directory matches input directory.");
514 +
515 + return fileInfo;
516 + }
517 + }
518 +}
src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs new
+649
@@ -0,0 +1,649 @@
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 +using System;
4 +using System.IO;
5 +using System.Text;
6 +using System.Collections.Generic;
7 +using System.Security.Cryptography;
8 +using Microsoft.VisualStudio.TestTools.UnitTesting;
9 +using WixToolset.Dtf.Compression;
10 +
11 +namespace WixToolset.Dtf.Test
12 +{
13 + public class CompressionTestUtil
14 + {
15 + private static MD5 md5 = new MD5CryptoServiceProvider();
16 +
17 + private string progressTextFile;
18 +
19 + public CompressionTestUtil(string progressTextFile)
20 + {
21 + this.progressTextFile = progressTextFile;
22 + }
23 +
24 + public static IList<int[]> ExpectedProgress
25 + {
26 + get { return CompressionTestUtil.expectedProgress; }
27 + set { CompressionTestUtil.expectedProgress = value; }
28 + }
29 + private static IList<int[]> expectedProgress;
30 +
31 + public void PrintArchiveProgress(object source, ArchiveProgressEventArgs e)
32 + {
33 + switch (e.ProgressType)
34 + {
35 + case ArchiveProgressType.StartFile:
36 + {
37 + Console.WriteLine("StartFile: {0}", e.CurrentFileName);
38 + } break;
39 + case ArchiveProgressType.FinishFile:
40 + {
41 + Console.WriteLine("FinishFile: {0}", e.CurrentFileName);
42 + } break;
43 + case ArchiveProgressType.StartArchive:
44 + {
45 + Console.WriteLine("StartArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName);
46 + } break;
47 + case ArchiveProgressType.FinishArchive:
48 + {
49 + Console.WriteLine("FinishArchive: {0} : {1}", e.CurrentArchiveNumber, e.CurrentArchiveName);
50 + } break;
51 + }
52 +
53 + File.AppendAllText(this.progressTextFile, e.ToString().Replace("\n", Environment.NewLine));
54 +
55 + if (CompressionTestUtil.expectedProgress != null &&
56 + e.ProgressType != ArchiveProgressType.PartialFile &&
57 + e.ProgressType != ArchiveProgressType.PartialArchive)
58 + {
59 + Assert.AreNotEqual<int>(0, CompressionTestUtil.expectedProgress.Count);
60 + int[] expected = CompressionTestUtil.expectedProgress[0];
61 + CompressionTestUtil.expectedProgress.RemoveAt(0);
62 + Assert.AreEqual<ArchiveProgressType>((ArchiveProgressType) expected[0], e.ProgressType, "Checking ProgressType.");
63 + Assert.AreEqual<int>(expected[1], e.CurrentFileNumber, "Checking CurrentFileNumber.");
64 + Assert.AreEqual<int>(expected[2], e.TotalFiles, "Checking TotalFiles.");
65 + Assert.AreEqual<int>(expected[4], e.CurrentArchiveNumber, "Checking CurrentArchiveNumber.");
66 + Assert.AreEqual<int>(expected[5], e.TotalArchives, "Checking TotalArchives.");
67 + }
68 + }
69 +
70 + public static bool CompareDirectories(string dirA, string dirB)
71 + {
72 + bool difference = false;
73 + Console.WriteLine("Comparing directories {0}, {1}", dirA, dirB);
74 +
75 + string[] filesA = Directory.GetFiles(dirA);
76 + string[] filesB = Directory.GetFiles(dirB);
77 + for (int iA = 0; iA < filesA.Length; iA++)
78 + {
79 + filesA[iA] = Path.GetFileName(filesA[iA]);
80 + }
81 + for (int iB = 0; iB < filesB.Length; iB++)
82 + {
83 + filesB[iB] = Path.GetFileName(filesB[iB]);
84 + }
85 + Array.Sort(filesA);
86 + Array.Sort(filesB);
87 +
88 + for (int iA = 0, iB = 0; iA < filesA.Length || iB < filesB.Length; )
89 + {
90 + int comp;
91 + if (iA == filesA.Length)
92 + {
93 + comp = 1;
94 + }
95 + else if (iB == filesB.Length)
96 + {
97 + comp = -1;
98 + }
99 + else
100 + {
101 + comp = String.Compare(filesA[iA], filesB[iB]);
102 + }
103 + if (comp < 0)
104 + {
105 + Console.WriteLine("< " + filesA[iA]);
106 + difference = true;
107 + iA++;
108 + }
109 + else if (comp > 0)
110 + {
111 + Console.WriteLine("> " + filesB[iB]);
112 + difference = true;
113 + iB++;
114 + }
115 + else
116 + {
117 + string fileA = Path.Combine(dirA, filesA[iA]);
118 + string fileB = Path.Combine(dirB, filesB[iB]);
119 +
120 + byte[] hashA;
121 + byte[] hashB;
122 +
123 + lock (CompressionTestUtil.md5)
124 + {
125 + using (Stream fileAStream = File.OpenRead(fileA))
126 + {
127 + hashA = CompressionTestUtil.md5.ComputeHash(fileAStream);
128 + }
129 + using (Stream fileBStream = File.OpenRead(fileB))
130 + {
131 + hashB = CompressionTestUtil.md5.ComputeHash(fileBStream);
132 + }
133 + }
134 +
135 + for (int i = 0; i < hashA.Length; i++)
136 + {
137 + if (hashA[i] != hashB[i])
138 + {
139 + Console.WriteLine("~ " + filesA[iA]);
140 + difference = true;
141 + break;
142 + }
143 + }
144 +
145 + iA++;
146 + iB++;
147 + }
148 + }
149 +
150 + string[] dirsA = Directory.GetDirectories(dirA);
151 + string[] dirsB = Directory.GetDirectories(dirB);
152 + for (int iA = 0; iA < dirsA.Length; iA++)
153 + {
154 + dirsA[iA] = Path.GetFileName(dirsA[iA]);
155 + }
156 + for (int iB = 0; iB < dirsB.Length; iB++)
157 + {
158 + dirsB[iB] = Path.GetFileName(dirsB[iB]);
159 + }
160 + Array.Sort(dirsA);
161 + Array.Sort(dirsB);
162 +
163 + for (int iA = 0, iB = 0; iA < dirsA.Length || iB < dirsB.Length; )
164 + {
165 + int comp;
166 + if (iA == dirsA.Length)
167 + {
168 + comp = 1;
169 + }
170 + else if (iB == dirsB.Length)
171 + {
172 + comp = -1;
173 + }
174 + else
175 + {
176 + comp = String.Compare(dirsA[iA], dirsB[iB]);
177 + }
178 + if (comp < 0)
179 + {
180 + Console.WriteLine("< {0}\\", dirsA[iA]);
181 + difference = true;
182 + iA++;
183 + }
184 + else if (comp > 0)
185 + {
186 + Console.WriteLine("> {1}\\", dirsB[iB]);
187 + difference = true;
188 + iB++;
189 + }
190 + else
191 + {
192 + string subDirA = Path.Combine(dirA, dirsA[iA]);
193 + string subDirB = Path.Combine(dirB, dirsB[iB]);
194 + if (!CompressionTestUtil.CompareDirectories(subDirA, subDirB))
195 + {
196 + difference = true;
197 + }
198 + iA++;
199 + iB++;
200 + }
201 + }
202 +
203 + return !difference;
204 + }
205 +
206 +
207 + public static void GenerateRandomFile(string path, int seed, long size)
208 + {
209 + Console.WriteLine("Generating random file {0} (seed={1}, size={2})",
210 + path, seed, size);
211 + Random random = new Random(seed);
212 + bool easy = random.Next(2) == 1;
213 + int chunk = 1024 * random.Next(1, 100);
214 + using (TextWriter tw = new StreamWriter(
215 + File.Create(path, 4096), Encoding.ASCII))
216 + {
217 + for (long count = 0; count < size; count++)
218 + {
219 + char c = (char) (easy ? random.Next('a', 'b' + 1)
220 + : random.Next(32, 127));
221 + tw.Write(c);
222 + if (--chunk == 0)
223 + {
224 + chunk = 1024 * random.Next(1, 101);
225 + easy = random.Next(2) == 1;
226 + }
227 + }
228 + }
229 + }
230 +
231 + public static void TestArchiveInfoNullParams(
232 + ArchiveInfo archiveInfo,
233 + string dirA,
234 + string dirB,
235 + string[] files)
236 + {
237 + Exception caughtEx = null;
238 + try
239 + {
240 + archiveInfo.PackFiles(null, null, files);
241 + }
242 + catch (Exception ex) { caughtEx = ex; }
243 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
244 + caughtEx = null;
245 + try
246 + {
247 + archiveInfo.PackFiles(null, files, new string[] { });
248 + }
249 + catch (Exception ex) { caughtEx = ex; }
250 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx);
251 + caughtEx = null;
252 + try
253 + {
254 + archiveInfo.PackFileSet(dirA, null);
255 + }
256 + catch (Exception ex) { caughtEx = ex; }
257 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
258 + caughtEx = null;
259 + try
260 + {
261 + archiveInfo.PackFiles(null, files, files);
262 + }
263 + catch (Exception ex) { caughtEx = ex; }
264 + Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx);
265 + caughtEx = null;
266 + try
267 + {
268 + archiveInfo.PackFiles(dirA, null, files);
269 + }
270 + catch (Exception ex) { caughtEx = ex; }
271 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
272 + caughtEx = null;
273 + try
274 + {
275 + archiveInfo.PackFiles(dirA, files, null);
276 + }
277 + catch (Exception ex) { caughtEx = ex; }
278 + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
279 +
280 + caughtEx = null;
281 + try
282 + {
283 + archiveInfo.CopyTo(null);
284 + }
285 + catch (Exception ex) { caughtEx = ex; }
286 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
287 + caughtEx = null;
288 + try
289 + {
290 + archiveInfo.CopyTo(null, true);
291 + }
292 + catch (Exception ex) { caughtEx = ex; }
293 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
294 + caughtEx = null;
295 + try
296 + {
297 + archiveInfo.MoveTo(null);
298 + }
299 + catch (Exception ex) { caughtEx = ex; }
300 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
301 + caughtEx = null;
302 + try
303 + {
304 + archiveInfo.GetFiles(null);
305 + }
306 + catch (Exception ex) { caughtEx = ex; }
307 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
308 + caughtEx = null;
309 + try
310 + {
311 + archiveInfo.UnpackFile(null, "test.txt");
312 + }
313 + catch (Exception ex) { caughtEx = ex; }
314 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
315 + caughtEx = null;
316 + try
317 + {
318 + archiveInfo.UnpackFile("test.txt", null);
319 + }
320 + catch (Exception ex) { caughtEx = ex; }
321 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
322 + caughtEx = null;
323 + try
324 + {
325 + archiveInfo.UnpackFiles(null, dirB, files);
326 + }
327 + catch (Exception ex) { caughtEx = ex; }
328 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
329 + caughtEx = null;
330 + try
331 + {
332 + archiveInfo.UnpackFiles(files, null, null);
333 + }
334 + catch (Exception ex) { caughtEx = ex; }
335 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
336 + caughtEx = null;
337 + try
338 + {
339 + archiveInfo.UnpackFiles(files, null, files);
340 + }
341 + catch (Exception ex) { caughtEx = ex; }
342 + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
343 + caughtEx = null;
344 + try
345 + {
346 + archiveInfo.UnpackFiles(files, dirB, null);
347 + }
348 + catch (Exception ex) { caughtEx = ex; }
349 + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
350 + caughtEx = null;
351 + try
352 + {
353 + archiveInfo.UnpackFiles(files, dirB, new string[] { });
354 + }
355 + catch (Exception ex) { caughtEx = ex; }
356 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentOutOfRangeException), "Caught exception: " + caughtEx);
357 + caughtEx = null;
358 + try
359 + {
360 + archiveInfo.UnpackFileSet(null, dirB);
361 + }
362 + catch (Exception ex) { caughtEx = ex; }
363 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
364 + }
365 +
366 + public static void TestCompressionEngineNullParams(
367 + CompressionEngine engine,
368 + ArchiveFileStreamContext streamContext,
369 + string[] testFiles)
370 + {
371 + Exception caughtEx;
372 +
373 + Console.WriteLine("Testing null streamContext.");
374 + caughtEx = null;
375 + try
376 + {
377 + engine.Pack(null, testFiles);
378 + }
379 + catch (Exception ex) { caughtEx = ex; }
380 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
381 + caughtEx = null;
382 + try
383 + {
384 + engine.Pack(null, testFiles, 0);
385 + }
386 + catch (Exception ex) { caughtEx = ex; }
387 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
388 +
389 + Console.WriteLine("Testing null files.");
390 + caughtEx = null;
391 + try
392 + {
393 + engine.Pack(streamContext, null);
394 + }
395 + catch (Exception ex) { caughtEx = ex; }
396 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
397 +
398 + Console.WriteLine("Testing null files.");
399 + caughtEx = null;
400 + try
401 + {
402 + engine.Pack(streamContext, null, 0);
403 + }
404 + catch (Exception ex) { caughtEx = ex; }
405 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
406 +
407 +
408 + Console.WriteLine("Testing null stream.");
409 + caughtEx = null;
410 + try
411 + {
412 + engine.IsArchive(null);
413 + }
414 + catch (Exception ex) { caughtEx = ex; }
415 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
416 + caughtEx = null;
417 + try
418 + {
419 + engine.FindArchiveOffset(null);
420 + }
421 + catch (Exception ex) { caughtEx = ex; }
422 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
423 + caughtEx = null;
424 + try
425 + {
426 + engine.GetFiles(null);
427 + }
428 + catch (Exception ex) { caughtEx = ex; }
429 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
430 + caughtEx = null;
431 + try
432 + {
433 + engine.GetFileInfo(null);
434 + }
435 + catch (Exception ex) { caughtEx = ex; }
436 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
437 + caughtEx = null;
438 + try
439 + {
440 + engine.Unpack(null, "testUnpack.txt");
441 + }
442 + catch (Exception ex) { caughtEx = ex; }
443 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
444 + Console.WriteLine("Testing null streamContext.");
445 + caughtEx = null;
446 + try
447 + {
448 + engine.GetFiles(null, null);
449 + }
450 + catch (Exception ex) { caughtEx = ex; }
451 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
452 + caughtEx = null;
453 + try
454 + {
455 + engine.GetFileInfo(null, null);
456 + }
457 + catch (Exception ex) { caughtEx = ex; }
458 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
459 + caughtEx = null;
460 + try
461 + {
462 + engine.Unpack((IUnpackStreamContext) null, null);
463 + }
464 + catch (Exception ex) { caughtEx = ex; }
465 + Assert.IsInstanceOfType(caughtEx, typeof(ArgumentNullException), "Caught exception: " + caughtEx);
466 + }
467 +
468 + public static void TestBadPackStreamContexts(
469 + CompressionEngine engine, string archiveName, string[] testFiles)
470 + {
471 + Exception caughtEx;
472 +
473 + Console.WriteLine("Testing streamContext that returns null from GetName.");
474 + caughtEx = null;
475 + try
476 + {
477 + engine.Pack(
478 + new MisbehavingStreamContext(archiveName, null, null, false, false, true, true, true, true),
479 + testFiles);
480 + }
481 + catch (Exception ex) { caughtEx = ex; }
482 + Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx);
483 + Console.WriteLine("Testing streamContext that returns null from OpenArchive.");
484 + caughtEx = null;
485 + try
486 + {
487 + engine.Pack(
488 + new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true),
489 + testFiles);
490 + }
491 + catch (Exception ex) { caughtEx = ex; }
492 + Assert.IsTrue(caughtEx is FileNotFoundException, "Caught exception: " + caughtEx);
493 + Console.WriteLine("Testing streamContext that returns null from OpenFile.");
494 + caughtEx = null;
495 + try
496 + {
497 + engine.Pack(
498 + new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true),
499 + testFiles);
500 + }
501 + catch (Exception ex) { caughtEx = ex; }
502 + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
503 + Console.WriteLine("Testing streamContext that throws on GetName.");
504 + caughtEx = null;
505 + try
506 + {
507 + engine.Pack(
508 + new MisbehavingStreamContext(archiveName, null, null, true, false, true, true, true, true),
509 + testFiles);
510 + }
511 + catch (Exception ex) { caughtEx = ex; }
512 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
513 + Console.WriteLine("Testing streamContext that throws on OpenArchive.");
514 + caughtEx = null;
515 + try
516 + {
517 + engine.Pack(
518 + new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true),
519 + testFiles);
520 + }
521 + catch (Exception ex) { caughtEx = ex; }
522 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
523 + Console.WriteLine("Testing streamContext that throws on CloseArchive.");
524 + caughtEx = null;
525 + try
526 + {
527 + engine.Pack(
528 + new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true),
529 + testFiles);
530 + }
531 + catch (Exception ex) { caughtEx = ex; }
532 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
533 + Console.WriteLine("Testing streamContext that throws on OpenFile.");
534 + caughtEx = null;
535 + try
536 + {
537 + engine.Pack(
538 + new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true),
539 + testFiles);
540 + }
541 + catch (Exception ex) { caughtEx = ex; }
542 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
543 + Console.WriteLine("Testing streamContext that throws on CloseFile.");
544 + caughtEx = null;
545 + try
546 + {
547 + engine.Pack(
548 + new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false),
549 + testFiles);
550 + }
551 + catch (Exception ex) { caughtEx = ex; }
552 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
553 + }
554 +
555 + public static void TestBadUnpackStreamContexts(
556 + CompressionEngine engine, string archiveName)
557 + {
558 + Exception caughtEx;
559 +
560 + Console.WriteLine("Testing streamContext that returns null from OpenArchive.");
561 + caughtEx = null;
562 + try
563 + {
564 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, false, true, true, true), null);
565 + }
566 + catch (Exception ex) { caughtEx = ex; }
567 + Assert.IsInstanceOfType(caughtEx, typeof(FileNotFoundException), "Caught exception: " + caughtEx);
568 + Console.WriteLine("Testing streamContext that returns null from OpenFile.");
569 + caughtEx = null;
570 + try
571 + {
572 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, false, true, true, true, false, true), null);
573 + }
574 + catch (Exception ex) { caughtEx = ex; }
575 + Assert.IsNull(caughtEx, "Caught exception: " + caughtEx);
576 + Console.WriteLine("Testing streamContext that throws on OpenArchive.");
577 + caughtEx = null;
578 + try
579 + {
580 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, false, true, true, true), null);
581 + }
582 + catch (Exception ex) { caughtEx = ex; }
583 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
584 + Console.WriteLine("Testing streamContext that throws on CloseArchive.");
585 + caughtEx = null;
586 + try
587 + {
588 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, false, true, true), null);
589 + }
590 + catch (Exception ex) { caughtEx = ex; }
591 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
592 + Console.WriteLine("Testing streamContext that throws on OpenFile.");
593 + caughtEx = null;
594 + try
595 + {
596 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, false, true), null);
597 + }
598 + catch (Exception ex) { caughtEx = ex; }
599 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
600 + Console.WriteLine("Testing streamContext that throws on CloseFile.");
601 + caughtEx = null;
602 + try
603 + {
604 + engine.Unpack(new MisbehavingStreamContext(archiveName, null, null, true, true, true, true, true, false), null);
605 + }
606 + catch (Exception ex) { caughtEx = ex; }
607 + Assert.IsTrue(caughtEx != null && caughtEx.Message == MisbehavingStreamContext.EXCEPTION, "Caught exception: " + caughtEx);
608 + }
609 +
610 + public static void TestTruncatedArchive(
611 + ArchiveInfo archiveInfo, Type expectedExceptionType)
612 + {
613 + for (long len = archiveInfo.Length - 1; len >= 0; len--)
614 + {
615 + string testArchive = String.Format("{0}.{1:d06}",
616 + archiveInfo.FullName, len);
617 + if (File.Exists(testArchive))
618 + {
619 + File.Delete(testArchive);
620 + }
621 +
622 + archiveInfo.CopyTo(testArchive);
623 + using (FileStream truncateStream =
624 + File.Open(testArchive, FileMode.Open, FileAccess.ReadWrite))
625 + {
626 + truncateStream.SetLength(len);
627 + }
628 +
629 + ArchiveInfo testArchiveInfo = (ArchiveInfo) archiveInfo.GetType()
630 + .GetConstructor(new Type[] { typeof(string) }).Invoke(new object[] { testArchive });
631 +
632 + Exception caughtEx = null;
633 + try
634 + {
635 + testArchiveInfo.GetFiles();
636 + }
637 + catch (Exception ex) { caughtEx = ex; }
638 + File.Delete(testArchive);
639 +
640 + if (caughtEx != null)
641 + {
642 + Assert.IsInstanceOfType(caughtEx, expectedExceptionType,
643 + String.Format("Caught exception listing archive truncated to {0}/{1} bytes",
644 + len, archiveInfo.Length));
645 + }
646 + }
647 + }
648 + }
649 +}
src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs new
+202
@@ -0,0 +1,202 @@
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 +using System;
4 +using System.IO;
5 +using System.Collections.Generic;
6 +using WixToolset.Dtf.Compression;
7 +
8 +namespace WixToolset.Dtf.Test
9 +{
10 + public class MisbehavingStreamContext : ArchiveFileStreamContext
11 + {
12 + public const string EXCEPTION = "Test exception.";
13 +
14 + private bool throwEx;
15 + private bool getName;
16 + private bool openArchive;
17 + private bool closeArchive;
18 + private bool openFile;
19 + private bool closeFile;
20 + private int closeFileCount;
21 +
22 + public MisbehavingStreamContext(
23 + string cabinetFile,
24 + string directory,
25 + IDictionary<string, string> files,
26 + bool throwEx,
27 + bool getName,
28 + bool openArchive,
29 + bool closeArchive,
30 + bool openFile,
31 + bool closeFile)
32 + : base(cabinetFile, directory, files)
33 + {
34 + this.throwEx = throwEx;
35 + this.getName = getName;
36 + this.openArchive = openArchive;
37 + this.closeArchive = closeArchive;
38 + this.openFile = openFile;
39 + this.closeFile = closeFile;
40 + }
41 +
42 + public override string GetArchiveName(int archiveNumber)
43 + {
44 + if (!this.getName)
45 + {
46 + if (throwEx)
47 + {
48 + throw new Exception(EXCEPTION);
49 + }
50 + else
51 + {
52 + return null;
53 + }
54 + }
55 + return base.GetArchiveName(archiveNumber);
56 + }
57 +
58 + public override Stream OpenArchiveWriteStream(
59 + int archiveNumber,
60 + string archiveName,
61 + bool truncate,
62 + CompressionEngine compressionEngine)
63 + {
64 + if (!this.openArchive)
65 + {
66 + if (throwEx)
67 + {
68 + throw new Exception(EXCEPTION);
69 + }
70 + else
71 + {
72 + return null;
73 + }
74 + }
75 + return base.OpenArchiveWriteStream(
76 + archiveNumber, archiveName, truncate, compressionEngine);
77 + }
78 +
79 + public override void CloseArchiveWriteStream(
80 + int archiveNumber,
81 + string archiveName,
82 + Stream stream)
83 + {
84 + if (!this.closeArchive)
85 + {
86 + if (throwEx)
87 + {
88 + this.closeArchive = true;
89 + throw new Exception(EXCEPTION);
90 + }
91 + return;
92 + }
93 + base.CloseArchiveWriteStream(archiveNumber, archiveName, stream);
94 + }
95 +
96 + public override Stream OpenFileReadStream(
97 + string path,
98 + out FileAttributes attributes,
99 + out DateTime lastWriteTime)
100 + {
101 + if (!this.openFile)
102 + {
103 + if (throwEx)
104 + {
105 + throw new Exception(EXCEPTION);
106 + }
107 + else
108 + {
109 + attributes = FileAttributes.Normal;
110 + lastWriteTime = DateTime.MinValue;
111 + return null;
112 + }
113 + }
114 + return base.OpenFileReadStream(path, out attributes, out lastWriteTime);
115 + }
116 +
117 + public override void CloseFileReadStream(string path, Stream stream)
118 + {
119 + if (!this.closeFile && ++closeFileCount == 2)
120 + {
121 + if (throwEx)
122 + {
123 + throw new Exception(EXCEPTION);
124 + }
125 + return;
126 + }
127 + base.CloseFileReadStream(path, stream);
128 + }
129 +
130 + public override Stream OpenArchiveReadStream(
131 + int archiveNumber,
132 + string archiveName,
133 + CompressionEngine compressionEngine)
134 + {
135 + if (!this.openArchive)
136 + {
137 + if (throwEx)
138 + {
139 + throw new Exception(EXCEPTION);
140 + }
141 + else
142 + {
143 + return null;
144 + }
145 + }
146 + return base.OpenArchiveReadStream(archiveNumber, archiveName, compressionEngine);
147 + }
148 +
149 + public override void CloseArchiveReadStream(
150 + int archiveNumber,
151 + string archiveName,
152 + Stream stream)
153 + {
154 + if (!this.closeArchive)
155 + {
156 + if (throwEx)
157 + {
158 + this.closeArchive = true;
159 + throw new Exception(EXCEPTION);
160 + }
161 + return;
162 + }
163 + base.CloseArchiveReadStream(archiveNumber, archiveName, stream);
164 + }
165 +
166 + public override Stream OpenFileWriteStream(
167 + string path,
168 + long fileSize,
169 + DateTime lastWriteTime)
170 + {
171 + if (!this.openFile)
172 + {
173 + if (throwEx)
174 + {
175 + throw new Exception(EXCEPTION);
176 + }
177 + else
178 + {
179 + return null;
180 + }
181 + }
182 + return base.OpenFileWriteStream(path, fileSize, lastWriteTime);
183 + }
184 +
185 + public override void CloseFileWriteStream(
186 + string path,
187 + Stream stream,
188 + FileAttributes attributes,
189 + DateTime lastWriteTime)
190 + {
191 + if (!this.closeFile && ++closeFileCount == 2)
192 + {
193 + if (throwEx)
194 + {
195 + throw new Exception(EXCEPTION);
196 + }
197 + return;
198 + }
199 + base.CloseFileWriteStream(path, stream, attributes, lastWriteTime);
200 + }
201 + }
202 +}
src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs new
+42
@@ -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 +
3 +using System;
4 +using System.Collections.Generic;
5 +using WixToolset.Dtf.Compression;
6 +
7 +namespace WixToolset.Dtf.Test
8 +{
9 + public class OptionStreamContext : ArchiveFileStreamContext
10 + {
11 + private PackOptionHandler packOptionHandler;
12 +
13 + public OptionStreamContext(IList<string> archiveFiles, string directory, IDictionary<string, string> files)
14 + : base(archiveFiles, directory, files)
15 + {
16 + }
17 +
18 + public delegate object PackOptionHandler(string optionName, object[] parameters);
19 +
20 + public PackOptionHandler OptionHandler
21 + {
22 + get
23 + {
24 + return this.packOptionHandler;
25 + }
26 + set
27 + {
28 + this.packOptionHandler = value;
29 + }
30 + }
31 +
32 + public override object GetOption(string optionName, object[] parameters)
33 + {
34 + if (this.OptionHandler == null)
35 + {
36 + return null;
37 + }
38 +
39 + return this.OptionHandler(optionName, parameters);
40 + }
41 + }
42 +}
src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj new
+31
@@ -0,0 +1,31 @@
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 ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 + <PropertyGroup>
6 + <ProjectGuid>{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}</ProjectGuid>
7 + <OutputType>Library</OutputType>
8 + <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 + <AssemblyName>WixToolsetTests.Dtf.Compression</AssemblyName>
10 + <CreateDocumentation>false</CreateDocumentation>
11 + </PropertyGroup>
12 +
13 + <ItemGroup>
14 + <Compile Include="CompressionTestUtil.cs" />
15 + <Compile Include="MisbehavingStreamContext.cs" />
16 + <Compile Include="OptionStreamContext.cs" />
17 + </ItemGroup>
18 +
19 + <ItemGroup>
20 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 + <Reference Include="System" />
22 + <Reference Include="System.Data" />
23 + <Reference Include="System.Xml" />
24 + </ItemGroup>
25 +
26 + <ItemGroup>
27 + <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
28 + </ItemGroup>
29 +
30 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
31 +</Project>
src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs new
+206
@@ -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 +
3 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Text;
8 + using System.Collections.Generic;
9 + using Microsoft.VisualStudio.TestTools.UnitTesting;
10 + using WixToolset.Dtf.WindowsInstaller;
11 +
12 + [TestClass]
13 + public class CustomActionTest
14 + {
15 + public CustomActionTest()
16 + {
17 + }
18 +
19 + [TestMethod]
20 + [Ignore] // Currently fails.
21 + public void CustomActionTest1()
22 + {
23 + InstallLogModes logEverything =
24 + InstallLogModes.FatalExit |
25 + InstallLogModes.Error |
26 + InstallLogModes.Warning |
27 + InstallLogModes.User |
28 + InstallLogModes.Info |
29 + InstallLogModes.ResolveSource |
30 + InstallLogModes.OutOfDiskSpace |
31 + InstallLogModes.ActionStart |
32 + InstallLogModes.ActionData |
33 + InstallLogModes.CommonData |
34 + InstallLogModes.Progress |
35 + InstallLogModes.Initialize |
36 + InstallLogModes.Terminate |
37 + InstallLogModes.ShowDialog;
38 +
39 + Installer.SetInternalUI(InstallUIOptions.Silent);
40 + ExternalUIHandler prevHandler = Installer.SetExternalUI(
41 + WindowsInstallerTest.ExternalUILogger, logEverything);
42 +
43 + try
44 + {
45 + string[] customActions = new string[] { "SampleCA1", "SampleCA2" };
46 + #if DEBUG
47 + string caDir = @"..\..\..\..\..\build\debug\x86\";
48 + #else
49 + string caDir = @"..\..\..\..\..\build\ship\x86\";
50 + #endif
51 + caDir = Path.GetFullPath(caDir);
52 + string caFile = "WixToolset.Dtf.Samples.ManagedCA.dll";
53 + string caProduct = "CustomActionTest.msi";
54 +
55 + this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, false);
56 +
57 + Exception caughtEx = null;
58 + try
59 + {
60 + Installer.InstallProduct(caProduct, String.Empty);
61 + }
62 + catch (Exception ex) { caughtEx = ex; }
63 + Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException),
64 + "Exception thrown while installing product: " + caughtEx);
65 +
66 + string arch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
67 + string arch2 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
68 + if (arch == "AMD64" || arch2 == "AMD64")
69 + {
70 + caDir = caDir.Replace("x86", "x64");
71 +
72 + this.CreateCustomActionProduct(caProduct, caDir + caFile, customActions, true);
73 +
74 + caughtEx = null;
75 + try
76 + {
77 + Installer.InstallProduct(caProduct, String.Empty);
78 + }
79 + catch (Exception ex) { caughtEx = ex; }
80 + Assert.IsInstanceOfType(caughtEx, typeof(InstallCanceledException),
81 + "Exception thrown while installing 64bit product: " + caughtEx);
82 + }
83 + }
84 + finally
85 + {
86 + Installer.SetExternalUI(prevHandler, InstallLogModes.None);
87 + }
88 + }
89 +
90 + private void CreateCustomActionProduct(
91 + string msiFile, string customActionFile, IList<string> customActions, bool sixtyFourBit)
92 + {
93 + using (Database db = new Database(msiFile, DatabaseOpenMode.CreateDirect))
94 + {
95 + WindowsInstallerUtils.InitializeProductDatabase(db, sixtyFourBit);
96 + WindowsInstallerUtils.CreateTestProduct(db);
97 +
98 + if (!File.Exists(customActionFile))
99 + throw new FileNotFoundException(customActionFile);
100 +
101 + using (Record binRec = new Record(2))
102 + {
103 + binRec[1] = Path.GetFileName(customActionFile);
104 + binRec.SetStream(2, customActionFile);
105 +
106 + db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec);
107 + }
108 +
109 + using (Record binRec2 = new Record(2))
110 + {
111 + binRec2[1] = "TestData";
112 + binRec2.SetStream(2, new MemoryStream(Encoding.UTF8.GetBytes("This is a test data stream.")));
113 +
114 + db.Execute("INSERT INTO `Binary` (`Name`, `Data`) VALUES (?, ?)", binRec2);
115 + }
116 +
117 + for (int i = 0; i < customActions.Count; i++)
118 + {
119 + db.Execute(
120 + "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('{0}', 1, '{1}', '{2}')",
121 + customActions[i],
122 + Path.GetFileName(customActionFile),
123 + customActions[i]);
124 + db.Execute(
125 + "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('{0}', '', {1})",
126 + customActions[i],
127 + 101 + i);
128 + }
129 +
130 + db.Execute("INSERT INTO `Property` (`Property`, `Value`) VALUES ('SampleCATest', 'TestValue')");
131 +
132 + db.Commit();
133 + }
134 + }
135 +
136 + [TestMethod]
137 + public void CustomActionData()
138 + {
139 + string dataString = "Key1=Value1;Key2=;Key3;Key4=Value=4;Key5";
140 + string dataString2 = "Key1=;Key2=Value2;Key3;Key4;Key6=Value;;6=6;Key7=Value7";
141 +
142 + CustomActionData data = new CustomActionData(dataString);
143 + Assert.AreEqual<string>(dataString, data.ToString());
144 +
145 + data["Key1"] = String.Empty;
146 + data["Key2"] = "Value2";
147 + data["Key4"] = null;
148 + data.Remove("Key5");
149 + data["Key6"] = "Value;6=6";
150 + data["Key7"] = "Value7";
151 +
152 + Assert.AreEqual<string>(dataString2, data.ToString());
153 +
154 + MyDataClass myData = new MyDataClass();
155 + myData.Member1 = "test1";
156 + myData.Member2 = "test2";
157 + data.AddObject("MyData", myData);
158 +
159 + string myDataString = data.ToString();
160 + CustomActionData data2 = new CustomActionData(myDataString);
161 +
162 + MyDataClass myData2 = data2.GetObject<MyDataClass>("MyData");
163 + Assert.AreEqual<MyDataClass>(myData, myData2);
164 +
165 + List<string> myComplexDataObject = new List<string>();
166 + myComplexDataObject.Add("CValue1");
167 + myComplexDataObject.Add("CValue2");
168 + myComplexDataObject.Add("CValue3");
169 +
170 + CustomActionData myComplexData = new CustomActionData();
171 + myComplexData.AddObject("MyComplexData", myComplexDataObject);
172 + myComplexData.AddObject("NestedData", data);
173 + string myComplexDataString = myComplexData.ToString();
174 +
175 + CustomActionData myComplexData2 = new CustomActionData(myComplexDataString);
176 + List<string> myComplexDataObject2 = myComplexData2.GetObject<List<string>>("MyComplexData");
177 +
178 + Assert.AreEqual<int>(myComplexDataObject.Count, myComplexDataObject2.Count);
179 + for (int i = 0; i < myComplexDataObject.Count; i++)
180 + {
181 + Assert.AreEqual<string>(myComplexDataObject[i], myComplexDataObject2[i]);
182 + }
183 +
184 + data2 = myComplexData2.GetObject<CustomActionData>("NestedData");
185 + Assert.AreEqual<string>(data.ToString(), data2.ToString());
186 + }
187 +
188 + public class MyDataClass
189 + {
190 + public string Member1;
191 + public string Member2;
192 +
193 + public override bool Equals(object obj)
194 + {
195 + MyDataClass other = obj as MyDataClass;
196 + return other != null && this.Member1 == other.Member1 && this.Member2 == other.Member2;
197 + }
198 +
199 + public override int GetHashCode()
200 + {
201 + return (this.Member1 != null ? this.Member1.GetHashCode() : 0) ^
202 + (this.Member2 != null ? this.Member2.GetHashCode() : 0);
203 + }
204 + }
205 + }
206 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj new
+28
@@ -0,0 +1,28 @@
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 +
5 +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 + <PropertyGroup>
7 + <ProjectGuid>{137D376B-989F-4FEA-9A67-01D8D38CA0DE}</ProjectGuid>
8 + <OutputType>Library</OutputType>
9 + <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
10 + <AssemblyName>WixToolsetTests.Dtf.WindowsInstaller.CustomActions</AssemblyName>
11 + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
12 + <CreateDocumentation>false</CreateDocumentation>
13 + </PropertyGroup>
14 +
15 + <ItemGroup>
16 + <Compile Include="CustomActionTest.cs" />
17 + </ItemGroup>
18 +
19 + <ItemGroup>
20 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
21 + <Reference Include="System" />
22 + <Reference Include="System.Windows.Forms" />
23 + <Reference Include="$(OutputPath)\WixToolset.Dtf.WindowsInstaller.dll" />
24 + <ProjectReference Include="..\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj" />
25 + </ItemGroup>
26 +
27 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
28 +</Project>
src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs new
+509
@@ -0,0 +1,509 @@
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 +using System;
4 +using System.Text;
5 +using System.Collections;
6 +using System.Collections.Generic;
7 +using System.Linq;
8 +using System.Linq.Expressions;
9 +using Microsoft.VisualStudio.TestTools.UnitTesting;
10 +using WixToolset.Dtf.WindowsInstaller;
11 +using WixToolset.Dtf.WindowsInstaller.Linq;
12 +
13 +namespace WixToolset.Dtf.Test
14 +{
15 + [TestClass]
16 + public class LinqTest
17 + {
18 + private void InitLinqTestDatabase(QDatabase db)
19 + {
20 + WindowsInstallerUtils.InitializeProductDatabase(db);
21 + WindowsInstallerUtils.CreateTestProduct(db);
22 +
23 + db.Execute(
24 + "INSERT INTO `Feature` (`Feature`, `Title`, `Description`, `Level`, `Attributes`) VALUES ('{0}', '{1}', '{2}', {3}, {4})",
25 + "TestFeature2",
26 + "Test Feature 2",
27 + "Test Feature 2 Description",
28 + 1,
29 + (int) FeatureAttributes.None);
30 +
31 + WindowsInstallerUtils.AddRegistryComponent(
32 + db, "TestFeature2", "MyTestRegComp",
33 + Guid.NewGuid().ToString("B"),
34 + "SOFTWARE\\Microsoft\\DTF\\Test",
35 + "MyTestRegComp", "test");
36 + WindowsInstallerUtils.AddRegistryComponent(
37 + db, "TestFeature2", "MyTestRegComp2",
38 + Guid.NewGuid().ToString("B"),
39 + "SOFTWARE\\Microsoft\\DTF\\Test",
40 + "MyTestRegComp2", "test2");
41 + WindowsInstallerUtils.AddRegistryComponent(
42 + db, "TestFeature2", "excludeComp",
43 + Guid.NewGuid().ToString("B"),
44 + "SOFTWARE\\Microsoft\\DTF\\Test",
45 + "MyTestRegComp3", "test3");
46 +
47 + db.Commit();
48 +
49 + db.Log = Console.Out;
50 + }
51 +
52 + [TestMethod]
53 + public void LinqSimple()
54 + {
55 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
56 + {
57 + this.InitLinqTestDatabase(db);
58 +
59 + var comps = from c in db.Components
60 + select c;
61 +
62 + int count = 0;
63 + foreach (var c in comps)
64 + {
65 + Console.WriteLine(c);
66 + count++;
67 + }
68 +
69 + Assert.AreEqual<int>(4, count);
70 + }
71 + }
72 +
73 + [TestMethod]
74 + public void LinqWhereNull()
75 + {
76 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
77 + {
78 + this.InitLinqTestDatabase(db);
79 +
80 + var features = from f in db.Features
81 + where f.Description != null
82 + select f;
83 +
84 + int count = 0;
85 + foreach (var f in features)
86 + {
87 + Console.WriteLine(f);
88 + Assert.AreEqual<string>("TestFeature2", f.Feature);
89 + count++;
90 + }
91 +
92 + Assert.AreEqual<int>(1, count);
93 +
94 + var features2 = from f in db.Features
95 + where f.Description == null
96 + select f;
97 +
98 + count = 0;
99 + foreach (var f in features2)
100 + {
101 + Console.WriteLine(f);
102 + Assert.AreEqual<string>("TestFeature1", f.Feature);
103 + count++;
104 + }
105 +
106 + Assert.AreEqual<int>(1, count);
107 + }
108 + }
109 +
110 + [TestMethod]
111 + public void LinqWhereOperators()
112 + {
113 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
114 + {
115 + this.InitLinqTestDatabase(db);
116 +
117 + for (int i = 0; i < 100; i++)
118 + {
119 + var newFile = db.Files.NewRecord();
120 + newFile.File = "TestFile" + i;
121 + newFile.Component_ = "TestComponent";
122 + newFile.FileName = "TestFile" + i + ".txt";
123 + newFile.FileSize = i % 10;
124 + newFile.Sequence = i;
125 + newFile.Insert();
126 + }
127 +
128 + var files1 = from f in db.Files where f.Sequence < 40 select f;
129 + Assert.AreEqual<int>(40, files1.AsEnumerable().Count());
130 +
131 + var files2 = from f in db.Files where f.Sequence <= 40 select f;
132 + Assert.AreEqual<int>(41, files2.AsEnumerable().Count());
133 +
134 + var files3 = from f in db.Files where f.Sequence > 40 select f;
135 + Assert.AreEqual<int>(59, files3.AsEnumerable().Count());
136 +
137 + var files4 = from f in db.Files where f.Sequence >= 40 select f;
138 + Assert.AreEqual<int>(60, files4.AsEnumerable().Count());
139 +
140 + var files5 = from f in db.Files where 40 < f.Sequence select f;
141 + Assert.AreEqual<int>(59, files5.AsEnumerable().Count());
142 +
143 + var files6 = from f in db.Files where 40 <= f.Sequence select f;
144 + Assert.AreEqual<int>(60, files6.AsEnumerable().Count());
145 +
146 + var files7 = from f in db.Files where 40 > f.Sequence select f;
147 + Assert.AreEqual<int>(40, files7.AsEnumerable().Count());
148 +
149 + var files8 = from f in db.Files where 40 >= f.Sequence select f;
150 + Assert.AreEqual<int>(41, files8.AsEnumerable().Count());
151 +
152 + var files9 = from f in db.Files where f.Sequence == 40 select f;
153 + Assert.AreEqual<int>(40, files9.AsEnumerable().First().Sequence);
154 +
155 + var files10 = from f in db.Files where f.Sequence != 40 select f;
156 + Assert.AreEqual<int>(99, files10.AsEnumerable().Count());
157 +
158 + var files11 = from f in db.Files where 40 == f.Sequence select f;
159 + Assert.AreEqual<int>(40, files11.AsEnumerable().First().Sequence);
160 +
161 + var files12 = from f in db.Files where 40 != f.Sequence select f;
162 + Assert.AreEqual<int>(99, files12.AsEnumerable().Count());
163 + }
164 + }
165 +
166 + [TestMethod]
167 + public void LinqShapeSelect()
168 + {
169 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
170 + {
171 + this.InitLinqTestDatabase(db);
172 +
173 + Console.WriteLine("Running LINQ query 1.");
174 + var features1 = from f in db.Features
175 + select new { Name = f.Feature,
176 + Desc = f.Description };
177 +
178 + int count = 0;
179 + foreach (var f in features1)
180 + {
181 + Console.WriteLine(f);
182 + count++;
183 + }
184 +
185 + Assert.AreEqual<int>(2, count);
186 +
187 + Console.WriteLine();
188 + Console.WriteLine("Running LINQ query 2.");
189 + var features2 = from f in db.Features
190 + where f.Description != null
191 + select new { Name = f.Feature,
192 + Desc = f.Description.ToLower() };
193 +
194 + count = 0;
195 + foreach (var f in features2)
196 + {
197 + Console.WriteLine(f);
198 + Assert.AreEqual<string>("TestFeature2", f.Name);
199 + count++;
200 + }
201 +
202 + Assert.AreEqual<int>(1, count);
203 + }
204 + }
205 +
206 + [TestMethod]
207 + public void LinqUpdateNullableString()
208 + {
209 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
210 + {
211 + this.InitLinqTestDatabase(db);
212 +
213 + string newDescription = "New updated feature description.";
214 +
215 + var features = from f in db.Features
216 + where f.Description != null
217 + select f;
218 +
219 + int count = 0;
220 + foreach (var f in features)
221 + {
222 + Console.WriteLine(f);
223 + Assert.AreEqual<string>("TestFeature2", f.Feature);
224 + f.Description = newDescription;
225 + count++;
226 + }
227 +
228 + Assert.AreEqual<int>(1, count);
229 +
230 + var features2 = from f in db.Features
231 + where f.Description == newDescription
232 + select f;
233 + count = 0;
234 + foreach (var f in features2)
235 + {
236 + Console.WriteLine(f);
237 + Assert.AreEqual<string>("TestFeature2", f.Feature);
238 + f.Description = null;
239 + count++;
240 + }
241 +
242 + Assert.AreEqual<int>(1, count);
243 +
244 + var features3 = from f in db.Features
245 + where f.Description == null
246 + select f.Feature;
247 + count = 0;
248 + foreach (var f in features3)
249 + {
250 + Console.WriteLine(f);
251 + count++;
252 + }
253 +
254 + Assert.AreEqual<int>(2, count);
255 +
256 + db.Commit();
257 + }
258 + }
259 +
260 + [TestMethod]
261 + public void LinqInsertDelete()
262 + {
263 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
264 + {
265 + this.InitLinqTestDatabase(db);
266 +
267 + var newProp = db.Properties.NewRecord();
268 + newProp.Property = "TestNewProp1";
269 + newProp.Value = "TestNewValue";
270 + newProp.Insert();
271 +
272 + string prop = (from p in db.Properties
273 + where p.Property == "TestNewProp1"
274 + select p.Value).AsEnumerable().First();
275 + Assert.AreEqual<string>("TestNewValue", prop);
276 +
277 + newProp.Delete();
278 +
279 + int propCount = (from p in db.Properties
280 + where p.Property == "TestNewProp1"
281 + select p.Value).AsEnumerable().Count();
282 + Assert.AreEqual<int>(0, propCount);
283 +
284 + db.Commit();
285 + }
286 + }
287 +
288 + [TestMethod]
289 + public void LinqQueryQRecord()
290 + {
291 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
292 + {
293 + this.InitLinqTestDatabase(db);
294 +
295 + var installFilesSeq = (from a in db["InstallExecuteSequence"]
296 + where a["Action"] == "InstallFiles"
297 + select a["Sequence"]).AsEnumerable().First();
298 + Assert.AreEqual<string>("4000", installFilesSeq);
299 + }
300 + }
301 +
302 + [TestMethod]
303 + public void LinqOrderBy()
304 + {
305 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
306 + {
307 + this.InitLinqTestDatabase(db);
308 +
309 + var actions = from a in db.InstallExecuteSequences
310 + orderby a.Sequence
311 + select a.Action;
312 + foreach (var a in actions)
313 + {
314 + Console.WriteLine(a);
315 + }
316 +
317 + var files = from f in db.Files
318 + orderby f.FileSize, f["Sequence"]
319 + where f.Attributes == FileAttributes.None
320 + select f;
321 +
322 + foreach (var f in files)
323 + {
324 + Console.WriteLine(f);
325 + }
326 + }
327 + }
328 +
329 + [TestMethod]
330 + public void LinqTwoWayJoin()
331 + {
332 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
333 + {
334 + this.InitLinqTestDatabase(db);
335 + int count;
336 +
337 + var regs = from r in db.Registries
338 + join c in db["Component"] on r.Component_ equals c["Component"]
339 + where c["Component"] == "MyTestRegComp" &&
340 + r.Root == RegistryRoot.UserOrMachine
341 + select new { Reg = r.Registry, Dir = c["Directory_"] };
342 +
343 + count = 0;
344 + foreach (var r in regs)
345 + {
346 + Console.WriteLine(r);
347 + count++;
348 + }
349 + Assert.AreEqual<int>(1, count);
350 +
351 + var regs2 = from r in db.Registries
352 + join c in db.Components on r.Component_ equals c.Component
353 + where c.Component == "MyTestRegComp" &&
354 + r.Root == RegistryRoot.UserOrMachine
355 + select new { Reg = r, Dir = c.Directory_ };
356 +
357 + count = 0;
358 + foreach (var r in regs2)
359 + {
360 + Assert.IsNotNull(r.Reg.Registry);
361 + Console.WriteLine(r);
362 + count++;
363 + }
364 + Assert.AreEqual<int>(1, count);
365 +
366 + var regs3 = from r in db.Registries
367 + join c in db.Components on r.Component_ equals c.Component
368 + where c.Component == "MyTestRegComp" &&
369 + r.Root == RegistryRoot.UserOrMachine
370 + select r;
371 +
372 + count = 0;
373 + foreach (var r in regs3)
374 + {
375 + Assert.IsNotNull(r.Registry);
376 + Console.WriteLine(r);
377 + count++;
378 + }
379 + Assert.AreEqual<int>(1, count);
380 +
381 + }
382 + }
383 +
384 + [TestMethod]
385 + public void LinqFourWayJoin()
386 + {
387 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
388 + {
389 + this.InitLinqTestDatabase(db);
390 + int count;
391 +
392 + IList<string> pretest = db.ExecuteStringQuery(
393 + "SELECT `Feature`.`Feature` " +
394 + "FROM `Feature`, `FeatureComponents`, `Component`, `Registry` " +
395 + "WHERE `Feature`.`Feature` = `FeatureComponents`.`Feature_` " +
396 + "AND `FeatureComponents`.`Component_` = `Component`.`Component` " +
397 + "AND `Component`.`Component` = `Registry`.`Component_` " +
398 + "AND (`Registry`.`Registry` = 'MyTestRegCompReg1')");
399 + Assert.AreEqual<int>(1, pretest.Count);
400 +
401 + var features = from f in db.Features
402 + join fc in db.FeatureComponents on f.Feature equals fc.Feature_
403 + join c in db.Components on fc.Component_ equals c.Component
404 + join r in db.Registries on c.Component equals r.Component_
405 + where r.Registry == "MyTestRegCompReg1"
406 + select f.Feature;
407 +
408 + count = 0;
409 + foreach (var featureName in features)
410 + {
411 + Console.WriteLine(featureName);
412 + count++;
413 + }
414 + Assert.AreEqual<int>(1, count);
415 +
416 + }
417 + }
418 +
419 + [TestMethod]
420 + public void EnumTable()
421 + {
422 + using (QDatabase db = new QDatabase("testlinq.msi", DatabaseOpenMode.Create))
423 + {
424 + this.InitLinqTestDatabase(db);
425 + int count = 0;
426 + foreach (var comp in db.Components)
427 + {
428 + Console.WriteLine(comp);
429 + count++;
430 + }
431 + Assert.AreNotEqual<int>(0, count);
432 + }
433 + }
434 +
435 + [TestMethod]
436 + public void DatabaseAsQueryable()
437 + {
438 + using (Database db = new Database("testlinq.msi", DatabaseOpenMode.Create))
439 + {
440 + WindowsInstallerUtils.InitializeProductDatabase(db);
441 + WindowsInstallerUtils.CreateTestProduct(db);
442 +
443 + var comps = from c in db.AsQueryable().Components
444 + select c;
445 +
446 + int count = 0;
447 + foreach (var c in comps)
448 + {
449 + Console.WriteLine(c);
450 + count++;
451 + }
452 +
453 + Assert.AreEqual<int>(1, count);
454 + }
455 + }
456 +
457 + [TestMethod]
458 + public void EnumProducts()
459 + {
460 + var products = from p in ProductInstallation.AllProducts
461 + where p.Publisher == ".NET Foundation"
462 + select new { Name = p.ProductName,
463 + Ver = p.ProductVersion,
464 + Company = p.Publisher,
465 + InstallDate = p.InstallDate,
466 + PackageCode = p.AdvertisedPackageCode };
467 +
468 + foreach (var p in products)
469 + {
470 + Console.WriteLine(p);
471 + Assert.IsTrue(p.Company == ".NET Foundation");
472 + }
473 + }
474 +
475 + [TestMethod]
476 + public void EnumFeatures()
477 + {
478 + foreach (var p in ProductInstallation.AllProducts)
479 + {
480 + Console.WriteLine(p.ProductName);
481 +
482 + foreach (var f in p.Features)
483 + {
484 + Console.WriteLine("\t" + f.FeatureName);
485 + }
486 + }
487 + }
488 +
489 + [TestMethod]
490 + public void EnumComponents()
491 + {
492 + var comps = from c in ComponentInstallation.AllComponents
493 + where c.State == InstallState.Local &&
494 + c.Product.Publisher == ".NET Foundation"
495 + select c.Path;
496 +
497 + int count = 0;
498 + foreach (var c in comps)
499 + {
500 + if (++count == 100) break;
501 +
502 + Console.WriteLine(c);
503 + }
504 +
505 + Assert.AreEqual<int>(100, count);
506 + }
507 + }
508 +
509 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj new
+31
@@ -0,0 +1,31 @@
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" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 + <PropertyGroup>
6 + <ProjectGuid>{4F55F9B8-D8B6-41EB-8796-221B4CD98324}</ProjectGuid>
7 + <OutputType>Library</OutputType>
8 + <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 + <AssemblyName>WixToolsetTests.Dtf.Linq</AssemblyName>
10 + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 + <CreateDocumentation>false</CreateDocumentation>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="LinqTest.cs" />
16 + </ItemGroup>
17 +
18 + <ItemGroup>
19 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
20 + <Reference Include="System" />
21 + <Reference Include="System.Core" />
22 + </ItemGroup>
23 +
24 + <ItemGroup>
25 + <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
26 + <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj" />
27 + <ProjectReference Include="..\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj" />
28 + </ItemGroup>
29 +
30 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
31 +</Project>
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs new
+173
@@ -0,0 +1,173 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Reflection;
8 + using System.Windows.Forms;
9 + using System.Globalization;
10 + using System.Collections.Generic;
11 + using Microsoft.VisualStudio.TestTools.UnitTesting;
12 + using WixToolset.Dtf.WindowsInstaller;
13 + using View = WixToolset.Dtf.WindowsInstaller.View;
14 +
15 + [TestClass]
16 + public class EmbeddedExternalUI
17 + {
18 + const InstallLogModes TestLogModes =
19 + InstallLogModes.FatalExit |
20 + InstallLogModes.Error |
21 + InstallLogModes.Warning |
22 + InstallLogModes.User |
23 + InstallLogModes.Info |
24 + InstallLogModes.ResolveSource |
25 + InstallLogModes.OutOfDiskSpace |
26 + InstallLogModes.ActionStart |
27 + InstallLogModes.ActionData |
28 + InstallLogModes.CommonData;
29 +
30 +#if DEBUG
31 + const string EmbeddedUISampleBinDir = @"..\..\build\debug\";
32 +#else
33 + const string EmbeddedUISampleBinDir = @"..\..\build\release\";
34 +#endif
35 +
36 + [TestMethod]
37 + [Ignore] // Requires elevation.
38 + public void EmbeddedUISingleInstall()
39 + {
40 + string dbFile = "EmbeddedUISingleInstall.msi";
41 + string productCode;
42 +
43 + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir);
44 + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll";
45 +
46 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
47 + {
48 + WindowsInstallerUtils.InitializeProductDatabase(db);
49 + WindowsInstallerUtils.CreateTestProduct(db);
50 +
51 + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
52 +
53 + using (Record uiRec = new Record(5))
54 + {
55 + uiRec[1] = "TestEmbeddedUI";
56 + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll";
57 + uiRec[3] = 1;
58 + uiRec[4] = (int) (
59 + EmbeddedExternalUI.TestLogModes |
60 + InstallLogModes.Progress |
61 + InstallLogModes.Initialize |
62 + InstallLogModes.Terminate |
63 + InstallLogModes.ShowDialog);
64 + uiRec.SetStream(5, Path.Combine(uiDir, uiFile));
65 + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec);
66 + }
67 +
68 + db.Commit();
69 + }
70 +
71 + Installer.SetInternalUI(InstallUIOptions.Full);
72 +
73 + ProductInstallation installation = new ProductInstallation(productCode);
74 + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
75 +
76 + Exception caughtEx = null;
77 + try
78 + {
79 + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "install.log");
80 + Installer.InstallProduct(dbFile, String.Empty);
81 + }
82 + catch (Exception ex) { caughtEx = ex; }
83 + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
84 +
85 + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed.");
86 + Console.WriteLine();
87 + Console.WriteLine();
88 + Console.WriteLine();
89 + Console.WriteLine("===================================================================");
90 + Console.WriteLine();
91 + Console.WriteLine();
92 + Console.WriteLine();
93 +
94 + try
95 + {
96 + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, "uninstall.log");
97 + Installer.InstallProduct(dbFile, "REMOVE=All");
98 + }
99 + catch (Exception ex) { caughtEx = ex; }
100 + Assert.IsNull(caughtEx, "Exception thrown while uninstalling product: " + caughtEx);
101 + }
102 +
103 + // This test does not pass if run normally.
104 + // It only passes when a failure is injected into the EmbeddedUI launcher.
105 + ////[TestMethod]
106 + public void EmbeddedUIInitializeFails()
107 + {
108 + string dbFile = "EmbeddedUIInitializeFails.msi";
109 + string productCode;
110 +
111 + string uiDir = Path.GetFullPath(EmbeddedExternalUI.EmbeddedUISampleBinDir);
112 + string uiFile = "WixToolset.Dtf.Samples.EmbeddedUI.dll";
113 +
114 + // A number that will be used to check whether a type 19 CA runs.
115 + const string magicNumber = "3.14159265358979323846264338327950";
116 +
117 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
118 + {
119 + WindowsInstallerUtils.InitializeProductDatabase(db);
120 + WindowsInstallerUtils.CreateTestProduct(db);
121 +
122 + const string failureActionName = "EmbeddedUIInitializeFails";
123 + db.Execute("INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) " +
124 + "VALUES ('{0}', 19, '', 'Logging magic number: {1}')", failureActionName, magicNumber);
125 +
126 + // This type 19 CA (launch condition) is given a condition of 'UILevel = 3' so that it only runs if the
127 + // installation is running in BASIC UI mode, which is what we expect if the EmbeddedUI fails to initialize.
128 + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) " +
129 + "VALUES ('{0}', 'UILevel = 3', 1)", failureActionName);
130 +
131 + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
132 +
133 + using (Record uiRec = new Record(5))
134 + {
135 + uiRec[1] = "TestEmbeddedUI";
136 + uiRec[2] = Path.GetFileNameWithoutExtension(uiFile) + ".Wrapper.dll";
137 + uiRec[3] = 1;
138 + uiRec[4] = (int)(
139 + EmbeddedExternalUI.TestLogModes |
140 + InstallLogModes.Progress |
141 + InstallLogModes.Initialize |
142 + InstallLogModes.Terminate |
143 + InstallLogModes.ShowDialog);
144 + uiRec.SetStream(5, Path.Combine(uiDir, uiFile));
145 + db.Execute(db.Tables["MsiEmbeddedUI"].SqlInsertString, uiRec);
146 + }
147 +
148 + db.Commit();
149 + }
150 +
151 + Installer.SetInternalUI(InstallUIOptions.Full);
152 +
153 + ProductInstallation installation = new ProductInstallation(productCode);
154 + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
155 +
156 + string logFile = "install.log";
157 + Exception caughtEx = null;
158 + try
159 + {
160 + Installer.EnableLog(EmbeddedExternalUI.TestLogModes, logFile);
161 + Installer.InstallProduct(dbFile, String.Empty);
162 + }
163 + catch (Exception ex) { caughtEx = ex; }
164 + Assert.IsInstanceOfType(caughtEx, typeof(InstallerException),
165 + "Excpected InstallerException installing product; caught: " + caughtEx);
166 +
167 + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed.");
168 +
169 + string logText = File.ReadAllText(logFile);
170 + Assert.IsTrue(logText.Contains(magicNumber), "Checking that the type 19 custom action ran.");
171 + }
172 + }
173 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs new
+238
@@ -0,0 +1,238 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 + using System.Text;
9 + using WixToolset.Dtf.WindowsInstaller;
10 +
11 + public static class Schema
12 + {
13 + public static IList<TableInfo> Tables
14 + {
15 + get
16 + {
17 + return new TableInfo[]
18 + {
19 + Binary,
20 + Component,
21 + CustomAction,
22 + Directory,
23 + EmbeddedUI,
24 + Feature,
25 + FeatureComponents,
26 + File,
27 + InstallExecuteSequence,
28 + Media,
29 + Property,
30 + Registry
31 + };
32 + }
33 + }
34 +
35 + #region Table data
36 +
37 + public static TableInfo Binary { get { return new TableInfo(
38 + "Binary",
39 + new ColumnInfo[]
40 + {
41 + new ColumnInfo("Name", typeof(String), 72, true),
42 + new ColumnInfo("Data", typeof(Stream), 0, true),
43 + },
44 + new string[] { "Name" });
45 + } }
46 +
47 + public static TableInfo Component { get { return new TableInfo(
48 + "Component",
49 + new ColumnInfo[]
50 + {
51 + new ColumnInfo("Component", typeof(String), 72, true),
52 + new ColumnInfo("ComponentId", typeof(String), 38, false),
53 + new ColumnInfo("Directory_", typeof(String), 72, true),
54 + new ColumnInfo("Attributes", typeof(Int16), 2, true),
55 + new ColumnInfo("Condition", typeof(String), 255, false),
56 + new ColumnInfo("KeyPath", typeof(String), 72, false),
57 + },
58 + new string[] { "Component" });
59 + } }
60 +
61 + public static TableInfo CustomAction { get { return new TableInfo(
62 + "CustomAction",
63 + new ColumnInfo[]
64 + {
65 + new ColumnInfo("Action", typeof(String), 72, true),
66 + new ColumnInfo("Type", typeof(Int16), 2, true),
67 + new ColumnInfo("Source", typeof(String), 64, false),
68 + new ColumnInfo("Target", typeof(String), 255, false),
69 + },
70 + new string[] { "Action" });
71 + } }
72 +
73 + public static TableInfo Directory { get { return new TableInfo(
74 + "Directory",
75 + new ColumnInfo[]
76 + {
77 + new ColumnInfo("Directory", typeof(String), 72, true),
78 + new ColumnInfo("Directory_Parent", typeof(String), 72, false),
79 + new ColumnInfo("DefaultDir", typeof(String), 255, true, false, true),
80 + },
81 + new string[] { "Directory" });
82 + } }
83 +
84 + public static TableInfo EmbeddedUI { get { return new TableInfo(
85 + "MsiEmbeddedUI",
86 + new ColumnInfo[]
87 + {
88 + new ColumnInfo("MsiEmbeddedUI", typeof(String), 72, true),
89 + new ColumnInfo("FileName", typeof(String), 72, true),
90 + new ColumnInfo("Attributes", typeof(Int16), 2, true),
91 + new ColumnInfo("MessageFilter", typeof(Int32), 4, false),
92 + new ColumnInfo("Data", typeof(Stream), 0, true),
93 + },
94 + new string[] { "MsiEmbeddedUI" });
95 + } }
96 +
97 + public static TableInfo Feature { get { return new TableInfo(
98 + "Feature",
99 + new ColumnInfo[]
100 + {
101 + new ColumnInfo("Feature", typeof(String), 38, true),
102 + new ColumnInfo("Feature_Parent", typeof(String), 38, false),
103 + new ColumnInfo("Title", typeof(String), 64, false, false, true),
104 + new ColumnInfo("Description", typeof(String), 64, false, false, true),
105 + new ColumnInfo("Display", typeof(Int16), 2, false),
106 + new ColumnInfo("Level", typeof(Int16), 2, true),
107 + new ColumnInfo("Directory_", typeof(String), 72, false),
108 + new ColumnInfo("Attributes", typeof(Int16), 2, true),
109 + },
110 + new string[] { "Feature" });
111 + } }
112 +
113 + public static TableInfo FeatureComponents { get { return new TableInfo(
114 + "FeatureComponents",
115 + new ColumnInfo[]
116 + {
117 + new ColumnInfo("Feature_", typeof(String), 38, true),
118 + new ColumnInfo("Component_", typeof(String), 72, true),
119 + },
120 + new string[] { "Feature_", "Component_" });
121 + } }
122 +
123 + public static TableInfo File { get { return new TableInfo(
124 + "File",
125 + new ColumnInfo[]
126 + {
127 + new ColumnInfo("File", typeof(String), 72, true),
128 + new ColumnInfo("Component_", typeof(String), 72, true),
129 + new ColumnInfo("FileName", typeof(String), 255, true, false, true),
130 + new ColumnInfo("FileSize", typeof(Int32), 4, true),
131 + new ColumnInfo("Version", typeof(String), 72, false),
132 + new ColumnInfo("Language", typeof(String), 20, false),
133 + new ColumnInfo("Attributes", typeof(Int16), 2, false),
134 + new ColumnInfo("Sequence", typeof(Int16), 2, true),
135 + },
136 + new string[] { "File" });
137 + } }
138 +
139 + public static TableInfo InstallExecuteSequence { get { return new TableInfo(
140 + "InstallExecuteSequence",
141 + new ColumnInfo[]
142 + {
143 + new ColumnInfo("Action", typeof(String), 72, true),
144 + new ColumnInfo("Condition", typeof(String), 255, false),
145 + new ColumnInfo("Sequence", typeof(Int16), 2, true),
146 + },
147 + new string[] { "Action" });
148 + } }
149 +
150 + public static TableInfo Media { get { return new TableInfo(
151 + "Media",
152 + new ColumnInfo[]
153 + {
154 + new ColumnInfo("DiskId", typeof(Int16), 2, true),
155 + new ColumnInfo("LastSequence", typeof(Int16), 2, true),
156 + new ColumnInfo("DiskPrompt", typeof(String), 64, false, false, true),
157 + new ColumnInfo("Cabinet", typeof(String), 255, false),
158 + new ColumnInfo("VolumeLabel", typeof(String), 32, false),
159 + new ColumnInfo("Source", typeof(String), 32, false),
160 + },
161 + new string[] { "DiskId" });
162 + } }
163 +
164 + public static TableInfo Property { get { return new TableInfo(
165 + "Property",
166 + new ColumnInfo[]
167 + {
168 + new ColumnInfo("Property", typeof(String), 72, true),
169 + new ColumnInfo("Value", typeof(String), 255, true),
170 + },
171 + new string[] { "Property" });
172 + } }
173 +
174 + public static TableInfo Registry { get { return new TableInfo(
175 + "Registry",
176 + new ColumnInfo[]
177 + {
178 + new ColumnInfo("Registry", typeof(String), 72, true),
179 + new ColumnInfo("Root", typeof(Int16), 2, true),
180 + new ColumnInfo("Key", typeof(String), 255, true, false, true),
181 + new ColumnInfo("Name", typeof(String), 255, false, false, true),
182 + new ColumnInfo("Value", typeof(String), 0, false, false, true),
183 + new ColumnInfo("Component_", typeof(String), 72, true),
184 + },
185 + new string[] { "Registry" });
186 + } }
187 +
188 + #endregion
189 +
190 + }
191 +
192 + public class Action
193 + {
194 + public readonly string Name;
195 + public readonly int Sequence;
196 +
197 + public Action(string name, int sequence)
198 + {
199 + this.Name = name;
200 + this.Sequence = sequence;
201 + }
202 +
203 + }
204 +
205 + public class Sequence
206 + {
207 + public static IList<Action> InstallExecute
208 + {
209 + get
210 + {
211 + return new Action[]
212 + {
213 + new Action("CostInitialize", 800),
214 + new Action("FileCost", 900),
215 + new Action("CostFinalize", 1000),
216 + new Action("InstallValidate", 1400),
217 + new Action("InstallInitialize", 1500),
218 + new Action("ProcessComponents", 1600),
219 + new Action("UnpublishComponents", 1700),
220 + new Action("UnpublishFeatures", 1800),
221 + new Action("RemoveRegistryValues", 2600),
222 + new Action("RemoveFiles", 3500),
223 + new Action("RemoveFolders", 3600),
224 + new Action("CreateFolders", 3700),
225 + new Action("MoveFiles", 3800),
226 + new Action("InstallFiles", 4000),
227 + new Action("WriteRegistryValues", 5000),
228 + new Action("RegisterProduct", 6100),
229 + new Action("PublishComponents", 6200),
230 + new Action("PublishFeatures", 6300),
231 + new Action("PublishProduct", 6400),
232 + new Action("InstallFinalize", 6600),
233 + };
234 + }
235 + }
236 +
237 + }
238 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs new
+409
@@ -0,0 +1,409 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Windows.Forms;
8 + using System.Globalization;
9 + using System.Collections.Generic;
10 + using Microsoft.VisualStudio.TestTools.UnitTesting;
11 + using WixToolset.Dtf.WindowsInstaller;
12 + using View = WixToolset.Dtf.WindowsInstaller.View;
13 +
14 + [TestClass]
15 + public class WindowsInstallerTest
16 + {
17 + public WindowsInstallerTest()
18 + {
19 + }
20 +
21 + [TestInitialize()]
22 + public void Initialize()
23 + {
24 + }
25 +
26 + [TestCleanup()]
27 + public void Cleanup()
28 + {
29 + }
30 +
31 + [TestMethod]
32 + [Ignore] // Currently fails.
33 + public void InstallerErrorMessages()
34 + {
35 + string msg3002 = Installer.GetErrorMessage(3002);
36 + Console.WriteLine("3002=" + msg3002);
37 + Assert.IsNotNull(msg3002);
38 + Assert.IsTrue(msg3002.Length > 0);
39 + }
40 +
41 + [TestMethod]
42 + public void InstallerDatabaseSchema()
43 + {
44 + string dbFile = "InstallerDatabaseSchema.msi";
45 +
46 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
47 + {
48 + WindowsInstallerUtils.InitializeProductDatabase(db);
49 + db.Commit();
50 + }
51 +
52 + Assert.IsTrue(File.Exists(dbFile), "Checking whether created database file " + dbFile + " exists.");
53 +
54 + using (Database db = new Database(dbFile, DatabaseOpenMode.ReadOnly))
55 + {
56 + TableCollection tables = db.Tables;
57 + Assert.AreEqual<int>(Schema.Tables.Count, tables.Count, "Counting tables.");
58 + Assert.AreEqual<int>(Schema.Property.Columns.Count, tables["Property"].Columns.Count, "Counting columns in Property table.");
59 +
60 + foreach (TableInfo tableInfo in tables)
61 + {
62 + Console.WriteLine(tableInfo.Name);
63 + foreach (ColumnInfo columnInfo in tableInfo.Columns)
64 + {
65 + Console.WriteLine("\t{0} {1}", columnInfo.Name, columnInfo.ColumnDefinitionString);
66 + }
67 + }
68 + }
69 + }
70 +
71 + [TestMethod]
72 + public void InstallerViewTables()
73 + {
74 + string dbFile = "InstallerViewTables.msi";
75 +
76 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
77 + {
78 + WindowsInstallerUtils.InitializeProductDatabase(db);
79 + db.Commit();
80 +
81 + using (View view1 = db.OpenView("SELECT `Property`, `Value` FROM `Property` WHERE `Value` IS NOT NULL"))
82 + {
83 + IList<TableInfo> viewTables = view1.Tables;
84 + Assert.IsNotNull(viewTables);
85 + Assert.AreEqual<int>(1, viewTables.Count);
86 + Assert.AreEqual<String>("Property", viewTables[0].Name);
87 + }
88 +
89 + using (View view2 = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES ('TestViewTables', 1)"))
90 + {
91 + IList<TableInfo> viewTables = view2.Tables;
92 + Assert.IsNotNull(viewTables);
93 + Assert.AreEqual<int>(1, viewTables.Count);
94 + Assert.AreEqual<String>("Property", viewTables[0].Name);
95 + }
96 +
97 + using (View view3 = db.OpenView("UPDATE `Property` SET `Value` = 2 WHERE `Property` = 'TestViewTables'"))
98 + {
99 + IList<TableInfo> viewTables = view3.Tables;
100 + Assert.IsNotNull(viewTables);
101 + Assert.AreEqual<int>(1, viewTables.Count);
102 + Assert.AreEqual<String>("Property", viewTables[0].Name);
103 + }
104 +
105 + using (View view4 = db.OpenView("alter table Property hold"))
106 + {
107 + IList<TableInfo> viewTables = view4.Tables;
108 + Assert.IsNotNull(viewTables);
109 + Assert.AreEqual<int>(1, viewTables.Count);
110 + Assert.AreEqual<String>("Property", viewTables[0].Name);
111 + }
112 + }
113 + }
114 +
115 + [TestMethod]
116 + public void InstallerInstallProduct()
117 + {
118 + string dbFile = "InstallerInstallProduct.msi";
119 + string productCode;
120 +
121 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
122 + {
123 + WindowsInstallerUtils.InitializeProductDatabase(db);
124 + WindowsInstallerUtils.CreateTestProduct(db);
125 +
126 + productCode = db.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
127 +
128 + db.Commit();
129 + }
130 +
131 + ProductInstallation installation = new ProductInstallation(productCode);
132 + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed before starting.");
133 +
134 + Installer.SetInternalUI(InstallUIOptions.Silent);
135 + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger,
136 + InstallLogModes.FatalExit |
137 + InstallLogModes.Error |
138 + InstallLogModes.Warning |
139 + InstallLogModes.User |
140 + InstallLogModes.Info |
141 + InstallLogModes.ResolveSource |
142 + InstallLogModes.OutOfDiskSpace |
143 + InstallLogModes.ActionStart |
144 + InstallLogModes.ActionData |
145 + InstallLogModes.CommonData |
146 + InstallLogModes.Progress |
147 + InstallLogModes.Initialize |
148 + InstallLogModes.Terminate |
149 + InstallLogModes.ShowDialog);
150 + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null.");
151 +
152 + Exception caughtEx = null;
153 + try
154 + {
155 + Installer.InstallProduct(dbFile, String.Empty);
156 + }
157 + catch (Exception ex) { caughtEx = ex; }
158 + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
159 +
160 + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None);
161 + Assert.AreEqual<ExternalUIHandler>(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned.");
162 +
163 + Assert.IsTrue(installation.IsInstalled, "Checking that product is installed.");
164 + Console.WriteLine();
165 + Console.WriteLine();
166 + Console.WriteLine();
167 + Console.WriteLine("===================================================================");
168 + Console.WriteLine();
169 + Console.WriteLine();
170 + Console.WriteLine();
171 +
172 + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger,
173 + InstallLogModes.FatalExit |
174 + InstallLogModes.Error |
175 + InstallLogModes.Warning |
176 + InstallLogModes.User |
177 + InstallLogModes.Info |
178 + InstallLogModes.ResolveSource |
179 + InstallLogModes.OutOfDiskSpace |
180 + InstallLogModes.ActionStart |
181 + InstallLogModes.ActionData |
182 + InstallLogModes.CommonData |
183 + InstallLogModes.Progress |
184 + InstallLogModes.Initialize |
185 + InstallLogModes.Terminate |
186 + InstallLogModes.ShowDialog);
187 + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null.");
188 +
189 + try
190 + {
191 + Installer.InstallProduct(dbFile, "REMOVE=All");
192 + }
193 + catch (Exception ex) { caughtEx = ex; }
194 + Assert.IsNull(caughtEx, "Exception thrown while installing product: " + caughtEx);
195 +
196 + Assert.IsFalse(installation.IsInstalled, "Checking that product is not installed after removing.");
197 +
198 + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None);
199 + Assert.AreEqual<ExternalUIRecordHandler>(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned.");
200 + }
201 +
202 + public static MessageResult ExternalUILogger(
203 + InstallMessage messageType,
204 + string message,
205 + MessageButtons buttons,
206 + MessageIcon icon,
207 + MessageDefaultButton defaultButton)
208 + {
209 + Console.WriteLine("{0}: {1}", messageType, message);
210 + return MessageResult.None;
211 + }
212 +
213 + public static MessageResult ExternalUIRecordLogger(
214 + InstallMessage messageType,
215 + Record messageRecord,
216 + MessageButtons buttons,
217 + MessageIcon icon,
218 + MessageDefaultButton defaultButton)
219 + {
220 + if (messageRecord != null)
221 + {
222 + if (messageRecord.FormatString.Length == 0 && messageRecord.FieldCount > 0)
223 + {
224 + messageRecord.FormatString = "1: [1] 2: [2] 3: [3] 4: [4] 5: [5]";
225 + }
226 + Console.WriteLine("{0}: {1}", messageType, messageRecord.ToString());
227 + }
228 + else
229 + {
230 + Console.WriteLine("{0}: (null)", messageType);
231 + }
232 + return MessageResult.None;
233 + }
234 +
235 + [TestMethod]
236 + [Ignore] // Currently fails.
237 + public void InstallerMessageResources()
238 + {
239 + string message1101 = Installer.GetErrorMessage(1101);
240 + Console.WriteLine("Message 1101: " + message1101);
241 + Assert.IsNotNull(message1101);
242 + Assert.IsTrue(message1101.Contains("file"));
243 +
244 + message1101 = Installer.GetErrorMessage(1101, CultureInfo.GetCultureInfo(1033));
245 + Console.WriteLine("Message 1101: " + message1101);
246 + Assert.IsNotNull(message1101);
247 + Assert.IsTrue(message1101.Contains("file"));
248 +
249 + string message2621 = Installer.GetErrorMessage(2621);
250 + Console.WriteLine("Message 2621: " + message2621);
251 + Assert.IsNotNull(message2621);
252 + Assert.IsTrue(message2621.Contains("DLL"));
253 +
254 + string message3002 = Installer.GetErrorMessage(3002);
255 + Console.WriteLine("Message 3002: " + message3002);
256 + Assert.IsNotNull(message3002);
257 + Assert.IsTrue(message3002.Contains("sequencing"));
258 + }
259 +
260 + [TestMethod]
261 + public void EnumComponentQualifiers()
262 + {
263 + foreach (ComponentInstallation comp in ComponentInstallation.AllComponents)
264 + {
265 + bool qualifiers = false;
266 + foreach (ComponentInstallation.Qualifier qualifier in comp.Qualifiers)
267 + {
268 + if (!qualifiers)
269 + {
270 + Console.WriteLine(comp.Path);
271 + qualifiers = true;
272 + }
273 +
274 + Console.WriteLine("\t{0}: {1}", qualifier.QualifierCode, qualifier.Data);
275 + }
276 + }
277 + }
278 +
279 + [TestMethod]
280 + public void DeleteRecord()
281 + {
282 + string dbFile = "DeleteRecord.msi";
283 +
284 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
285 + {
286 + WindowsInstallerUtils.InitializeProductDatabase(db);
287 + WindowsInstallerUtils.CreateTestProduct(db);
288 +
289 + string query = "SELECT `Property`, `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'";
290 +
291 + using (View view = db.OpenView(query))
292 + {
293 + view.Execute();
294 +
295 + Record rec = view.Fetch();
296 +
297 + Console.WriteLine("Calling ToString() : " + rec);
298 +
299 + view.Delete(rec);
300 + }
301 +
302 + Assert.AreEqual(0, db.ExecuteStringQuery(query).Count);
303 + }
304 + }
305 +
306 + [TestMethod]
307 + public void InsertRecordThenTryFormatString()
308 + {
309 + string dbFile = "InsertRecordThenTryFormatString.msi";
310 +
311 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
312 + {
313 + WindowsInstallerUtils.InitializeProductDatabase(db);
314 + WindowsInstallerUtils.CreateTestProduct(db);
315 +
316 + string parameterFormatString = "[1]";
317 + string[] properties = new string[]
318 + {
319 + "SonGoku", "Over 9000",
320 + };
321 +
322 + string query = "SELECT `Property`, `Value` FROM `Property`";
323 +
324 + using (View view = db.OpenView(query))
325 + {
326 + using (Record rec = new Record(2))
327 + {
328 + rec[1] = properties[0];
329 + rec[2] = properties[1];
330 + rec.FormatString = parameterFormatString;
331 + Console.WriteLine("Format String before inserting: " + rec.FormatString);
332 + view.Insert(rec);
333 +
334 + Console.WriteLine("Format String after inserting: " + rec.FormatString);
335 + // After inserting, the format string is invalid.
336 + Assert.AreEqual(String.Empty, rec.ToString());
337 +
338 + // Setting the format string manually makes it valid again.
339 + rec.FormatString = parameterFormatString;
340 + Assert.AreEqual(properties[0], rec.ToString());
341 + }
342 + }
343 + }
344 + }
345 +
346 + [TestMethod]
347 + public void SeekRecordThenTryFormatString()
348 + {
349 + string dbFile = "SeekRecordThenTryFormatString.msi";
350 +
351 + using (Database db = new Database(dbFile, DatabaseOpenMode.CreateDirect))
352 + {
353 + WindowsInstallerUtils.InitializeProductDatabase(db);
354 + WindowsInstallerUtils.CreateTestProduct(db);
355 +
356 + string parameterFormatString = "[1]";
357 + string[] properties = new string[]
358 + {
359 + "SonGoku", "Over 9000",
360 + };
361 +
362 + string query = "SELECT `Property`, `Value` FROM `Property`";
363 +
364 + using (View view = db.OpenView(query))
365 + {
366 + using (Record rec = new Record(2))
367 + {
368 + rec[1] = properties[0];
369 + rec[2] = properties[1];
370 + rec.FormatString = parameterFormatString;
371 + Console.WriteLine("Record fields before seeking: " + rec[0] + " " + rec[1] + " " + rec[2]);
372 + view.Seek(rec);
373 +
374 + //TODO: Why does view.Seek remove the record fields?
375 + Console.WriteLine("Record fields after seeking: " + rec[0] + " " + rec[1] + " " + rec[2]);
376 + // After inserting, the format string is invalid.
377 + Assert.AreEqual(String.Empty, rec.ToString());
378 + }
379 + }
380 + }
381 + }
382 +
383 + [TestMethod]
384 + public void TestToString()
385 + {
386 + string defaultString = "1: ";
387 + string vegetaShout = "It's OVER 9000!!";
388 + string gokuPowerLevel = "9001";
389 + string nappaInquiry = "Vegeta, what's the Scouter say about his power level?";
390 + string parameterFormatString = "[1]";
391 +
392 + Record rec = new Record(1);
393 + Assert.AreEqual(defaultString, rec.ToString(), "Testing default FormatString");
394 +
395 + rec.FormatString = String.Empty;
396 + Assert.AreEqual(defaultString, rec.ToString(), "Explicitly set the FormatString to the empty string.");
397 +
398 + rec.FormatString = vegetaShout;
399 + Assert.AreEqual(vegetaShout, rec.ToString(), "Testing text only (empty FormatString)");
400 +
401 + rec.FormatString = gokuPowerLevel;
402 + Assert.AreEqual(gokuPowerLevel, rec.ToString(), "Testing numbers only from a record that wasn't fetched.");
403 +
404 + Record rec2 = new Record(nappaInquiry);
405 + rec2.FormatString = parameterFormatString;
406 + Assert.AreEqual(nappaInquiry, rec2.ToString(), "Testing text with a FormatString set.");
407 + }
408 + }
409 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs new
+161
@@ -0,0 +1,161 @@
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 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Windows.Forms;
8 + using System.Globalization;
9 + using System.Collections.Generic;
10 + using Microsoft.VisualStudio.TestTools.UnitTesting;
11 + using WixToolset.Dtf.WindowsInstaller;
12 + using View = WixToolset.Dtf.WindowsInstaller.View;
13 +
14 + [TestClass]
15 + public class WindowsInstallerTransactions
16 + {
17 + [TestInitialize()]
18 + public void Initialize()
19 + {
20 + }
21 +
22 + [TestCleanup()]
23 + public void Cleanup()
24 + {
25 + }
26 +
27 + [TestMethod]
28 + [Ignore] // Requires elevation.
29 + public void InstallerTransactTwoProducts()
30 + {
31 + string dbFile1 = "InstallerTransactProduct1.msi";
32 + string dbFile2 = "InstallerTransactProduct2.msi";
33 + string productCode1;
34 + string productCode2;
35 +
36 + using (Database db1 = new Database(dbFile1, DatabaseOpenMode.CreateDirect))
37 + {
38 + WindowsInstallerUtils.InitializeProductDatabase(db1);
39 + WindowsInstallerUtils.CreateTestProduct(db1);
40 +
41 + productCode1 = db1.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
42 +
43 + db1.Commit();
44 + }
45 +
46 + using (Database db2 = new Database(dbFile2, DatabaseOpenMode.CreateDirect))
47 + {
48 + WindowsInstallerUtils.InitializeProductDatabase(db2);
49 + WindowsInstallerUtils.CreateTestProduct(db2);
50 +
51 + productCode2 = db2.ExecuteStringQuery("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'")[0];
52 +
53 + db2.Commit();
54 + }
55 +
56 + ProductInstallation installation1 = new ProductInstallation(productCode1);
57 + ProductInstallation installation2 = new ProductInstallation(productCode2);
58 + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed before starting.");
59 + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed before starting.");
60 +
61 + Installer.SetInternalUI(InstallUIOptions.Silent);
62 + ExternalUIHandler prevHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUILogger,
63 + InstallLogModes.FatalExit |
64 + InstallLogModes.Error |
65 + InstallLogModes.Warning |
66 + InstallLogModes.User |
67 + InstallLogModes.Info |
68 + InstallLogModes.ResolveSource |
69 + InstallLogModes.OutOfDiskSpace |
70 + InstallLogModes.ActionStart |
71 + InstallLogModes.ActionData |
72 + InstallLogModes.CommonData |
73 + InstallLogModes.Progress |
74 + InstallLogModes.Initialize |
75 + InstallLogModes.Terminate |
76 + InstallLogModes.ShowDialog);
77 + Assert.IsNull(prevHandler, "Checking that returned previous UI handler is null.");
78 +
79 + Transaction transaction = new Transaction("TestInstallTransaction", TransactionAttributes.None);
80 +
81 + Exception caughtEx = null;
82 + try
83 + {
84 + Installer.InstallProduct(dbFile1, String.Empty);
85 + }
86 + catch (Exception ex) { caughtEx = ex; }
87 + Assert.IsNull(caughtEx, "Exception thrown while installing product 1: " + caughtEx);
88 +
89 + Console.WriteLine();
90 + Console.WriteLine("===================================================================");
91 + Console.WriteLine();
92 +
93 + try
94 + {
95 + Installer.InstallProduct(dbFile2, String.Empty);
96 + }
97 + catch (Exception ex) { caughtEx = ex; }
98 + Assert.IsNull(caughtEx, "Exception thrown while installing product 2: " + caughtEx);
99 +
100 + transaction.Commit();
101 + transaction.Close();
102 +
103 + prevHandler = Installer.SetExternalUI(prevHandler, InstallLogModes.None);
104 + Assert.AreEqual<ExternalUIHandler>(WindowsInstallerTest.ExternalUILogger, prevHandler, "Checking that previously-set UI handler is returned.");
105 +
106 + Assert.IsTrue(installation1.IsInstalled, "Checking that product 1 is installed.");
107 + Assert.IsTrue(installation2.IsInstalled, "Checking that product 2 is installed.");
108 +
109 + Console.WriteLine();
110 + Console.WriteLine();
111 + Console.WriteLine();
112 + Console.WriteLine("===================================================================");
113 + Console.WriteLine("===================================================================");
114 + Console.WriteLine();
115 + Console.WriteLine();
116 + Console.WriteLine();
117 +
118 + ExternalUIRecordHandler prevRecHandler = Installer.SetExternalUI(WindowsInstallerTest.ExternalUIRecordLogger,
119 + InstallLogModes.FatalExit |
120 + InstallLogModes.Error |
121 + InstallLogModes.Warning |
122 + InstallLogModes.User |
123 + InstallLogModes.Info |
124 + InstallLogModes.ResolveSource |
125 + InstallLogModes.OutOfDiskSpace |
126 + InstallLogModes.ActionStart |
127 + InstallLogModes.ActionData |
128 + InstallLogModes.CommonData |
129 + InstallLogModes.Progress |
130 + InstallLogModes.Initialize |
131 + InstallLogModes.Terminate |
132 + InstallLogModes.ShowDialog);
133 + Assert.IsNull(prevRecHandler, "Checking that returned previous UI record handler is null.");
134 +
135 + transaction = new Transaction("TestUninstallTransaction", TransactionAttributes.None);
136 +
137 + try
138 + {
139 + Installer.InstallProduct(dbFile1, "REMOVE=All");
140 + }
141 + catch (Exception ex) { caughtEx = ex; }
142 + Assert.IsNull(caughtEx, "Exception thrown while removing product 1: " + caughtEx);
143 +
144 + try
145 + {
146 + Installer.InstallProduct(dbFile2, "REMOVE=All");
147 + }
148 + catch (Exception ex) { caughtEx = ex; }
149 + Assert.IsNull(caughtEx, "Exception thrown while removing product 2: " + caughtEx);
150 +
151 + transaction.Commit();
152 + transaction.Close();
153 +
154 + Assert.IsFalse(installation1.IsInstalled, "Checking that product 1 is not installed after removing.");
155 + Assert.IsFalse(installation2.IsInstalled, "Checking that product 2 is not installed after removing.");
156 +
157 + prevRecHandler = Installer.SetExternalUI(prevRecHandler, InstallLogModes.None);
158 + Assert.AreEqual<ExternalUIRecordHandler>(WindowsInstallerTest.ExternalUIRecordLogger, prevRecHandler, "Checking that previously-set UI record handler is returned.");
159 + }
160 + }
161 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs new
+174
@@ -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 +
3 +namespace WixToolset.Dtf.Test
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Text;
8 + using WixToolset.Dtf.WindowsInstaller;
9 +
10 + public class WindowsInstallerUtils
11 + {
12 + public static void InitializeProductDatabase(Database db)
13 + {
14 + InitializeProductDatabase(db, false);
15 + }
16 +
17 + public static void InitializeProductDatabase(Database db, bool sixtyFourBit)
18 + {
19 + db.SummaryInfo.CodePage = (short) Encoding.Default.CodePage;
20 + db.SummaryInfo.Title = "Windows Installer Test";
21 + db.SummaryInfo.Subject = db.SummaryInfo.Title;
22 + db.SummaryInfo.Author = typeof(WindowsInstallerUtils).Assembly.FullName;
23 + db.SummaryInfo.CreatingApp = db.SummaryInfo.Author;
24 + db.SummaryInfo.Comments = typeof(WindowsInstallerUtils).FullName + ".CreateBasicDatabase()";
25 + db.SummaryInfo.Keywords = "Installer,MSI,Database";
26 + db.SummaryInfo.PageCount = 300;
27 + db.SummaryInfo.WordCount = 0;
28 + db.SummaryInfo.RevisionNumber = Guid.NewGuid().ToString("B").ToUpper();
29 + db.SummaryInfo.Template = (sixtyFourBit ? "x64" : "Intel") + ";0";
30 +
31 + foreach (TableInfo tableInfo in Schema.Tables)
32 + {
33 + db.Execute(tableInfo.SqlCreateString);
34 + }
35 +
36 + db.Execute("INSERT INTO `Directory` (`Directory`, `DefaultDir`) VALUES ('TARGETDIR', 'SourceDir')");
37 + db.Execute("INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES ('ProgramFilesFolder', 'TARGETDIR', '.')");
38 +
39 + foreach (Action action in Sequence.InstallExecute)
40 + {
41 + db.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('{0}', {1})",
42 + action.Name, action.Sequence);
43 + }
44 + }
45 +
46 + public const string UpgradeCode = "{05955FE8-005F-4695-A81F-D559338065BB}";
47 +
48 + public static void CreateTestProduct(Database db)
49 + {
50 + Guid productGuid = Guid.NewGuid();
51 +
52 + string[] properties = new string[]
53 + {
54 + "ProductCode", productGuid.ToString("B").ToUpper(),
55 + "UpgradeCode", UpgradeCode,
56 + "ProductName", "Windows Installer Test Product " + productGuid.ToString("P").ToUpper(),
57 + "ProductVersion", "1.0.0.0000",
58 + };
59 +
60 + using (View view = db.OpenView("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)"))
61 + {
62 + using (Record rec = new Record(2))
63 + {
64 + for (int i = 0; i < properties.Length; i += 2)
65 + {
66 + rec[1] = properties[i];
67 + rec[2] = properties[i + 1];
68 + view.Execute(rec);
69 + }
70 + }
71 + }
72 +
73 + int randomId = new Random().Next(10000);
74 + string productDir = "TestDir" + randomId;
75 + db.Execute(
76 + "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) " +
77 + "VALUES ('TestDir', 'ProgramFilesFolder', 'TestDir|{0}:.')", productDir);
78 +
79 + string compId = Guid.NewGuid().ToString("B").ToUpper();
80 + db.Execute(
81 + "INSERT INTO `Component` " +
82 + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
83 + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
84 + "TestRegComp1",
85 + compId,
86 + "TestDir",
87 + (int) ComponentAttributes.RegistryKeyPath,
88 + "TestReg1");
89 +
90 + string productReg = "TestReg" + randomId;
91 + db.Execute(
92 + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}')",
93 + "TestReg1",
94 + -1,
95 + @"Software\Microsoft\Windows Installer Test\" + productReg,
96 + "TestRegComp1");
97 +
98 + db.Execute(
99 + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})",
100 + "TestFeature1",
101 + "Test Feature 1",
102 + 1,
103 + (int) FeatureAttributes.None);
104 +
105 + db.Execute(
106 + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
107 + "TestFeature1",
108 + "TestRegComp1");
109 + }
110 +
111 + public static void AddFeature(Database db, string featureName)
112 + {
113 + db.Execute(
114 + "INSERT INTO `Feature` (`Feature`, `Title`, `Level`, `Attributes`) VALUES ('{0}', '{1}', {2}, {3})",
115 + featureName,
116 + featureName,
117 + 1,
118 + (int) FeatureAttributes.None);
119 + }
120 +
121 + public static void AddRegistryComponent(Database db,
122 + string featureName, string compName, string compId,
123 + string keyName, string keyValueName, string value)
124 + {
125 + db.Execute(
126 + "INSERT INTO `Component` " +
127 + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
128 + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
129 + compName,
130 + compId,
131 + "TestDir",
132 + (int) ComponentAttributes.RegistryKeyPath,
133 + compName + "Reg1");
134 + db.Execute(
135 + "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('{0}', {1}, '{2}', '{3}', '{4}', '{5}')",
136 + compName + "Reg1",
137 + -1,
138 + @"Software\Microsoft\Windows Installer Test\" + keyName,
139 + keyValueName,
140 + value,
141 + compName);
142 + db.Execute(
143 + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
144 + featureName,
145 + compName);
146 + }
147 +
148 + public static void AddFileComponent(Database db,
149 + string featureName, string compName, string compId,
150 + string fileKey, string fileName)
151 + {
152 + db.Execute(
153 + "INSERT INTO `Component` " +
154 + "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) " +
155 + "VALUES ('{0}', '{1}', '{2}', {3}, '{4}')",
156 + compName,
157 + compId,
158 + "TestDir",
159 + (int) ComponentAttributes.None,
160 + fileKey);
161 + db.Execute(
162 + "INSERT INTO `File` " +
163 + "(`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) " +
164 + "VALUES ('{0}', '{1}', '{2}', 1, 0, 1)",
165 + fileKey,
166 + compName,
167 + fileName);
168 + db.Execute(
169 + "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES ('{0}', '{1}')",
170 + featureName,
171 + compName);
172 + }
173 + }
174 +}
src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj new
+34
@@ -0,0 +1,34 @@
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" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 + <PropertyGroup>
6 + <ProjectGuid>{16F5202F-9276-4166-975C-C9654BAF8012}</ProjectGuid>
7 + <OutputType>Library</OutputType>
8 + <RootNamespace>WixToolsetTests.Dtf</RootNamespace>
9 + <AssemblyName>WixToolsetTests.Dtf.WindowsInstaller</AssemblyName>
10 + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
11 + <CreateDocumentation>false</CreateDocumentation>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="EmbeddedExternalUI.cs" />
16 + <Compile Include="Schema.cs" />
17 + <Compile Include="WindowsInstallerTest.cs" />
18 + <Compile Include="WindowsInstallerTransactions.cs" />
19 + <Compile Include="WindowsInstallerUtils.cs" />
20 + </ItemGroup>
21 +
22 + <ItemGroup>
23 + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
24 + <Reference Include="System" />
25 + <Reference Include="System.Windows.Forms" />
26 + <Reference Include="System.Xml" />
27 + </ItemGroup>
28 +
29 + <ItemGroup>
30 + <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj" />
31 + </ItemGroup>
32 +
33 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
34 +</Project>
src/dtf/appveyor.cmd new
+28
@@ -0,0 +1,28 @@
1 +@setlocal
2 +@pushd %~dp0
3 +@set _C=Release
4 +@set _P=%~dp0build\%_C%\publish
5 +
6 +nuget restore || exit /b
7 +
8 +msbuild -p:Configuration=%_C% || exit /b
9 +
10 +msbuild src\Tools\SfxCA\SfxCA.vcxproj -p:Configuration=%_C% -p:Platform="x64" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\\" || exit /b
11 +msbuild src\Tools\SfxCA\SfxCA.vcxproj -p:Configuration=%_C% -p:Platform="x86" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\\" || exit /b
12 +
13 +msbuild src\Tools\MakeSfxCA\MakeSfxCA.csproj -p:Configuration=%_C% -p:TargetFramework="net461" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\net461" || exit /b
14 +msbuild src\Tools\MakeSfxCA\MakeSfxCA.csproj -p:Configuration=%_C% -p:TargetFramework="netcoreapp3.1" -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild\tools\netcoreapp3.1" || exit /b
15 +
16 +msbuild src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj -p:Configuration=%_C% -p:OutputPath="%_P%\WixToolset.Dtf.MSBuild" || exit /b
17 +msbuild src\WixToolset.Dtf.MSBuild\WixToolset.Dtf.MSBuild.csproj -target:Pack -p:Configuration=%_C% || exit /b
18 +
19 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression || exit /b
20 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression.Cab || exit /b
21 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Compression.Zip || exit /b
22 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.Resources || exit /b
23 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller || exit /b
24 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller.Linq || exit /b
25 +msbuild -t:Pack -p:Configuration=%_C% src\WixToolset.Dtf.WindowsInstaller.Package || exit /b
26 +
27 +@popd
28 +@endlocal
\ No newline at end of file
src/dtf/appveyor.yml new
+42
@@ -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 +#
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 +
6 +branches:
7 + only:
8 + - master
9 + - develop
10 +
11 +image: Visual Studio 2019
12 +
13 +version: 0.0.0.{build}
14 +configuration: Release
15 +
16 +environment:
17 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 + DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 + NUGET_XMLDOC_MODE: skip
20 +
21 +build_script:
22 + - appveyor.cmd
23 +
24 +pull_requests:
25 + do_not_increment_build_number: true
26 +
27 +nuget:
28 + disable_publish_on_pr: true
29 +
30 +skip_branch_with_pr: true
31 +skip_tags: true
32 +
33 +artifacts:
34 +- path: build\Release\**\*.nupkg
35 + name: nuget
36 +- path: build\Release\**\*.msi
37 + name: msi
38 +
39 +notifications:
40 +- provider: Slack
41 + incoming_webhook:
42 + secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA=
src/dtf/nuget.config new
+7
@@ -0,0 +1,7 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<configuration>
3 + <packageSources>
4 + <clear />
5 + <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
6 + </packageSources>
7 +</configuration>
\ No newline at end of file
src/samples/Dtf/DDiff/CabDiffEngine.cs new
+131
@@ -0,0 +1,131 @@
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 +using System;
4 +using System.IO;
5 +using System.Collections;
6 +using System.Collections.Generic;
7 +using WixToolset.Dtf.Compression.Cab;
8 +
9 +namespace WixToolset.Dtf.Samples.DDiff
10 +{
11 + public class CabDiffEngine : IDiffEngine
12 + {
13 + public CabDiffEngine()
14 + {
15 + }
16 +
17 + private bool IsCabinetFile(string file)
18 + {
19 + using(FileStream fileStream = File.OpenRead(file))
20 + {
21 + return new CabEngine().IsArchive(fileStream);
22 + }
23 + }
24 +
25 + public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
26 + {
27 + if(diffInput1 != null && File.Exists(diffInput1) &&
28 + diffInput2 != null && File.Exists(diffInput2) &&
29 + (IsCabinetFile(diffInput1) || IsCabinetFile(diffInput2)))
30 + {
31 + return .80f;
32 + }
33 + else
34 + {
35 + return 0;
36 + }
37 + }
38 +
39 + public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
40 + {
41 + bool difference = false;
42 + IComparer caseInsComp = CaseInsensitiveComparer.Default;
43 +
44 + // TODO: Make this faster by extracting the whole cab at once.
45 + // TODO: Optimize for the match case by first comparing the whole cab files.
46 +
47 + CabInfo cab1 = new CabInfo(diffInput1);
48 + CabInfo cab2 = new CabInfo(diffInput2);
49 + IList<CabFileInfo> cabFilesList1 = cab1.GetFiles();
50 + IList<CabFileInfo> cabFilesList2 = cab2.GetFiles();
51 + CabFileInfo[] cabFiles1 = new CabFileInfo[cabFilesList1.Count];
52 + CabFileInfo[] cabFiles2 = new CabFileInfo[cabFilesList2.Count];
53 + cabFilesList1.CopyTo(cabFiles1, 0);
54 + cabFilesList2.CopyTo(cabFiles2, 0);
55 + string[] files1 = new string[cabFiles1.Length];
56 + string[] files2 = new string[cabFiles2.Length];
57 + for(int i1 = 0; i1 < cabFiles1.Length; i1++) files1[i1] = cabFiles1[i1].Name;
58 + for(int i2 = 0; i2 < cabFiles2.Length; i2++) files2[i2] = cabFiles2[i2].Name;
59 + Array.Sort(files1, cabFiles1, caseInsComp);
60 + Array.Sort(files2, cabFiles2, caseInsComp);
61 +
62 +
63 + for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
64 + {
65 + int comp;
66 + if(i1 == files1.Length)
67 + {
68 + comp = 1;
69 + }
70 + else if(i2 == files2.Length)
71 + {
72 + comp = -1;
73 + }
74 + else
75 + {
76 + comp = caseInsComp.Compare(files1[i1], files2[i2]);
77 + }
78 + if(comp < 0)
79 + {
80 + diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
81 + i1++;
82 + difference = true;
83 + }
84 + else if(comp > 0)
85 + {
86 + diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
87 + i2++;
88 + difference = true;
89 + }
90 + else
91 + {
92 + string tempFile1 = Path.GetTempFileName();
93 + string tempFile2 = Path.GetTempFileName();
94 + cabFiles1[i1].CopyTo(tempFile1, true);
95 + cabFiles2[i2].CopyTo(tempFile2, true);
96 + IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
97 + StringWriter sw = new StringWriter();
98 + if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
99 + {
100 + diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
101 + diffOutput.Write(sw.ToString());
102 + difference = true;
103 + }
104 +
105 + File.SetAttributes(tempFile1, File.GetAttributes(tempFile1) & ~FileAttributes.ReadOnly);
106 + File.SetAttributes(tempFile2, File.GetAttributes(tempFile2) & ~FileAttributes.ReadOnly);
107 + try
108 + {
109 + File.Delete(tempFile1);
110 + File.Delete(tempFile2);
111 + }
112 + catch(IOException)
113 + {
114 +#if DEBUG
115 + Console.WriteLine("Could not delete temporary files {0} and {1}", tempFile1, tempFile2);
116 +#endif
117 + }
118 + i1++;
119 + i2++;
120 + }
121 + }
122 +
123 + return difference;
124 + }
125 +
126 + public virtual IDiffEngine Clone()
127 + {
128 + return new CabDiffEngine();
129 + }
130 + }
131 +}
src/samples/Dtf/DDiff/DDiff.cs new
+72
@@ -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 +
3 +using System;
4 +using System.IO;
5 +using System.Text;
6 +
7 +namespace WixToolset.Dtf.Samples.DDiff
8 +{
9 + public class DDiff
10 + {
11 + public static void Usage(TextWriter w)
12 + {
13 + w.WriteLine("Usage: DDiff target1 target2 [options]");
14 + w.WriteLine("Example: DDiff d:\\dir1 d:\\dir2");
15 + w.WriteLine("Example: DDiff patch1.msp patch2.msp /patchtarget target.msi");
16 + w.WriteLine();
17 + w.WriteLine("Options:");
18 + w.WriteLine(" /o [filename] Output results to text file (UTF8)");
19 + w.WriteLine(" /p [package.msi] Diff patches relative to target MSI");
20 + }
21 +
22 + public static int Main(string[] args)
23 + {
24 + if(args.Length < 2)
25 + {
26 + Usage(Console.Out);
27 + return -1;
28 + }
29 +
30 + string input1 = args[0];
31 + string input2 = args[1];
32 + string[] options = new string[args.Length - 2];
33 + for(int i = 0; i < options.Length; i++) options[i] = args[i+2];
34 +
35 + TextWriter output = Console.Out;
36 +
37 + for(int i = 0; i < options.Length - 1; i++)
38 + {
39 + switch(options[i].ToLower())
40 + {
41 + case "/o": goto case "-output";
42 + case "-o": goto case "-output";
43 + case "/output": goto case "-output";
44 + case "-output": output = new StreamWriter(options[i+1], false, Encoding.UTF8); break;
45 + }
46 + }
47 +
48 + IDiffEngineFactory diffFactory = new BestQualityDiffEngineFactory(new IDiffEngine[]
49 + {
50 + new DirectoryDiffEngine(),
51 + new FileDiffEngine(),
52 + new VersionedFileDiffEngine(),
53 + new TextFileDiffEngine(),
54 + new MsiDiffEngine(),
55 + new CabDiffEngine(),
56 + new MspDiffEngine(),
57 + });
58 +
59 + IDiffEngine diffEngine = diffFactory.GetDiffEngine(input1, input2, options);
60 + if(diffEngine != null)
61 + {
62 + bool different = diffEngine.GetDiff(input1, input2, options, output, "", diffFactory);
63 + return different ? 1 : 0;
64 + }
65 + else
66 + {
67 + Console.Error.WriteLine("Dont know how to diff those inputs.");
68 + return -1;
69 + }
70 + }
71 + }
72 +}
src/samples/Dtf/DDiff/DDiff.csproj new
+39
@@ -0,0 +1,39 @@
1 +
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 + <PropertyGroup>
7 + <ProjectGuid>{1CDF4242-4C00-4744-BBCD-085128978FF3}</ProjectGuid>
8 + <OutputType>Exe</OutputType>
9 + <RootNamespace>WixToolset.Dtf.Samples.DDiff</RootNamespace>
10 + <AssemblyName>DDiff</AssemblyName>
11 + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 + <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
13 + </PropertyGroup>
14 +
15 + <ItemGroup>
16 + <Compile Include="CabDiffEngine.cs" />
17 + <Compile Include="DDiff.cs" />
18 + <Compile Include="DirectoryDiffEngine.cs" />
19 + <Compile Include="FileDiffEngine.cs" />
20 + <Compile Include="IDiffEngine.cs" />
21 + <Compile Include="MsiDiffEngine.cs" />
22 + <Compile Include="MspDiffEngine.cs" />
23 + <Compile Include="TextFileDiffEngine.cs" />
24 + <Compile Include="VersionedFileDiffEngine.cs" />
25 + </ItemGroup>
26 +
27 + <ItemGroup>
28 + <Reference Include="System" />
29 + <Reference Include="System.Data" />
30 + <Reference Include="System.Xml" />
31 + <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
32 + <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
33 + <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
34 + <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
35 + <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
36 + </ItemGroup>
37 +
38 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
39 +</Project>
src/samples/Dtf/DDiff/DirectoryDiffEngine.cs new
+154
@@ -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 +
3 +using System;
4 +using System.IO;
5 +using System.Collections;
6 +
7 +namespace WixToolset.Dtf.Samples.DDiff
8 +{
9 + public class DirectoryDiffEngine : IDiffEngine
10 + {
11 + public DirectoryDiffEngine()
12 + {
13 + }
14 +
15 + public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
16 + {
17 + if(diffInput1 != null && Directory.Exists(diffInput1) &&
18 + diffInput2 != null && Directory.Exists(diffInput2))
19 + {
20 + return .70f;
21 + }
22 + else
23 + {
24 + return 0;
25 + }
26 + }
27 +
28 + public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
29 + {
30 + bool difference = false;
31 + IComparer caseInsComp = CaseInsensitiveComparer.Default;
32 +
33 + string[] files1 = Directory.GetFiles(diffInput1);
34 + string[] files2 = Directory.GetFiles(diffInput2);
35 + for(int i1 = 0; i1 < files1.Length; i1++)
36 + {
37 + files1[i1] = Path.GetFileName(files1[i1]);
38 + }
39 + for(int i2 = 0; i2 < files2.Length; i2++)
40 + {
41 + files2[i2] = Path.GetFileName(files2[i2]);
42 + }
43 + Array.Sort(files1, caseInsComp);
44 + Array.Sort(files2, caseInsComp);
45 +
46 + for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
47 + {
48 + int comp;
49 + if(i1 == files1.Length)
50 + {
51 + comp = 1;
52 + }
53 + else if(i2 == files2.Length)
54 + {
55 + comp = -1;
56 + }
57 + else
58 + {
59 + comp = caseInsComp.Compare(files1[i1], files2[i2]);
60 + }
61 + if(comp < 0)
62 + {
63 + diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
64 + i1++;
65 + difference = true;
66 + }
67 + else if(comp > 0)
68 + {
69 + diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
70 + i2++;
71 + difference = true;
72 + }
73 + else
74 + {
75 + string file1 = Path.Combine(diffInput1, files1[i1]);
76 + string file2 = Path.Combine(diffInput2, files2[i2]);
77 + IDiffEngine diffEngine = diffFactory.GetDiffEngine(file1, file2, options);
78 + StringWriter sw = new StringWriter();
79 + if(diffEngine.GetDiff(file1, file2, options, sw, linePrefix + " ", diffFactory))
80 + {
81 + diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
82 + diffOutput.Write(sw.ToString());
83 + difference = true;
84 + }
85 + i1++;
86 + i2++;
87 + }
88 + }
89 +
90 + string[] dirs1 = Directory.GetDirectories(diffInput1);
91 + string[] dirs2 = Directory.GetDirectories(diffInput2);
92 + for(int i1 = 0; i1 < dirs1.Length; i1++)
93 + {
94 + dirs1[i1] = Path.GetFileName(dirs1[i1]);
95 + }
96 + for(int i2 = 0; i2 < dirs2.Length; i2++)
97 + {
98 + dirs2[i2] = Path.GetFileName(dirs2[i2]);
99 + }
100 + Array.Sort(dirs1, caseInsComp);
101 + Array.Sort(dirs2, caseInsComp);
102 +
103 + for(int i1 = 0, i2 = 0; i1 < dirs1.Length || i2 < dirs2.Length; )
104 + {
105 + int comp;
106 + if(i1 == dirs1.Length)
107 + {
108 + comp = 1;
109 + }
110 + else if(i2 == dirs2.Length)
111 + {
112 + comp = -1;
113 + }
114 + else
115 + {
116 + comp = caseInsComp.Compare(dirs1[i1], dirs2[i2]);
117 + }
118 + if(comp < 0)
119 + {
120 + diffOutput.WriteLine("{0}< {1}", linePrefix, dirs1[i1]);
121 + i1++;
122 + difference = true;
123 + }
124 + else if(comp > 0)
125 + {
126 + diffOutput.WriteLine("{0}> {1}", linePrefix, dirs2[i2]);
127 + i2++;
128 + difference = true;
129 + }
130 + else
131 + {
132 + string dir1 = Path.Combine(diffInput1, dirs1[i1]);
133 + string dir2 = Path.Combine(diffInput2, dirs2[i2]);
134 + IDiffEngine diffEngine = diffFactory.GetDiffEngine(dir1, dir2, options);
135 + StringWriter sw = new StringWriter();
136 + if(diffEngine.GetDiff(dir1, dir2, options, sw, linePrefix + " ", diffFactory))
137 + {
138 + diffOutput.WriteLine("{0}{1}\\", linePrefix, dirs1[i1]);
139 + diffOutput.Write(sw.ToString());
140 + difference = true;
141 + }
142 + i1++;
143 + i2++;
144 + }
145 + }
146 + return difference;
147 + }
148 +
149 + public virtual IDiffEngine Clone()
150 + {
151 + return new DirectoryDiffEngine();
152 + }
153 + }
154 +}
src/samples/Dtf/DDiff/FileDiffEngine.cs new
+83
@@ -0,0 +1,83 @@
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 +using System;
4 +using System.IO;
5 +
6 +namespace WixToolset.Dtf.Samples.DDiff
7 +{
8 + public class FileDiffEngine : IDiffEngine
9 + {
10 + public FileDiffEngine()
11 + {
12 + }
13 +
14 + public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
15 + {
16 + if(diffInput1 != null && File.Exists(diffInput1) &&
17 + diffInput2 != null && File.Exists(diffInput2))
18 + {
19 + return .10f;
20 + }
21 + else
22 + {
23 + return 0;
24 + }
25 + }
26 +
27 + public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
28 + {
29 + bool difference = false;
30 +
31 + FileInfo file1 = new FileInfo(diffInput1);
32 + FileInfo file2 = new FileInfo(diffInput2);
33 +
34 + if(file1.Length != file2.Length)
35 + {
36 + diffOutput.WriteLine("{0}File size: {1} -> {2}", linePrefix, file1.Length, file2.Length);
37 + difference = true;
38 + }
39 + else
40 + {
41 + FileStream stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
42 + FileStream stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
43 +
44 + byte[] buf1 = new byte[512];
45 + byte[] buf2 = new byte[512];
46 +
47 + while(!difference)
48 + {
49 + int count1 = stream1.Read(buf1, 0, buf1.Length);
50 + int count2 = stream2.Read(buf2, 0, buf2.Length);
51 +
52 + for(int i = 0; i < count1; i++)
53 + {
54 + if(i == count2 || buf1[i] != buf2[i])
55 + {
56 + difference = true;
57 + break;
58 + }
59 + }
60 + if(count1 < buf1.Length) // EOF
61 + {
62 + break;
63 + }
64 + }
65 +
66 + stream1.Close();
67 + stream2.Close();
68 +
69 + if(difference)
70 + {
71 + diffOutput.WriteLine("{0}Files differ.", linePrefix);
72 + }
73 + }
74 +
75 + return difference;
76 + }
77 +
78 + public virtual IDiffEngine Clone()
79 + {
80 + return new FileDiffEngine();
81 + }
82 + }
83 +}
src/samples/Dtf/DDiff/IDiffEngine.cs new
+68
@@ -0,0 +1,68 @@
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 +using System;
4 +using System.IO;
5 +using System.Collections;
6 +
7 +namespace WixToolset.Dtf.Samples.DDiff
8 +{
9 + public interface IDiffEngine
10 + {
11 + float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory);
12 +
13 + bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory);
14 +
15 + IDiffEngine Clone();
16 + }
17 +
18 + public interface IDiffEngineFactory
19 + {
20 + IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options);
21 + }
22 +
23 + public class BestQualityDiffEngineFactory : IDiffEngineFactory
24 + {
25 + public virtual IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options)
26 + {
27 + float bestDiffQuality = 0;
28 + IDiffEngine bestDiffEngine = null;
29 +
30 + foreach(IDiffEngine diffEngine in diffEngines)
31 + {
32 + float diffQuality = diffEngine.GetDiffQuality(diffInput1, diffInput2, options, this);
33 + if(diffQuality > bestDiffQuality)
34 + {
35 + bestDiffQuality = diffQuality;
36 + bestDiffEngine = diffEngine;
37 + }
38 + }
39 + return (bestDiffEngine != null ? bestDiffEngine.Clone() : null);
40 + }
41 +
42 + public BestQualityDiffEngineFactory() : this(null) { }
43 + public BestQualityDiffEngineFactory(IDiffEngine[] diffEngines)
44 + {
45 + this.diffEngines = (diffEngines != null ? new ArrayList(diffEngines) : new ArrayList());
46 + }
47 +
48 + protected IList diffEngines;
49 +
50 + public virtual void Add(IDiffEngine diffEngine)
51 + {
52 + diffEngines.Add(diffEngine);
53 + }
54 +
55 + public virtual void Remove(IDiffEngine diffEngine)
56 + {
57 + diffEngines.Remove(diffEngine);
58 + }
59 +
60 + public IList DiffEngines
61 + {
62 + get
63 + {
64 + return ArrayList.ReadOnly(diffEngines);
65 + }
66 + }
67 + }
68 +}
src/samples/Dtf/DDiff/MsiDiffEngine.cs new
+276
@@ -0,0 +1,276 @@
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 +using System;
4 +using System.IO;
5 +using System.Collections;
6 +using System.Collections.Generic;
7 +using WixToolset.Dtf.WindowsInstaller;
8 +using WixToolset.Dtf.WindowsInstaller.Package;
9 +
10 +namespace WixToolset.Dtf.Samples.DDiff
11 +{
12 + public class MsiDiffEngine : IDiffEngine
13 + {
14 + public MsiDiffEngine()
15 + {
16 + }
17 +
18 + protected bool IsMsiDatabase(string file)
19 + {
20 + // TODO: use something smarter?
21 + switch(Path.GetExtension(file).ToLower())
22 + {
23 + case ".msi": return true;
24 + case ".msm": return true;
25 + case ".pcp": return true;
26 + default : return false;
27 + }
28 + }
29 +
30 + protected bool IsMspPatch(string file)
31 + {
32 + // TODO: use something smarter?
33 + switch(Path.GetExtension(file).ToLower())
34 + {
35 + case ".msp": return true;
36 + default : return false;
37 + }
38 + }
39 +
40 + public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
41 + {
42 + if(diffInput1 != null && File.Exists(diffInput1) &&
43 + diffInput2 != null && File.Exists(diffInput2) &&
44 + (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2)))
45 + {
46 + return .70f;
47 + }
48 + else if(diffInput1 != null && File.Exists(diffInput1) &&
49 + diffInput2 != null && File.Exists(diffInput2) &&
50 + (IsMspPatch(diffInput1) || IsMspPatch(diffInput2)))
51 + {
52 + return .60f;
53 + }
54 + else
55 + {
56 + return 0;
57 + }
58 + }
59 +
60 + public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
61 + {
62 + bool difference = false;
63 + Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
64 + Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
65 +
66 + if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
67 + if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
68 + if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
69 +
70 + db1.Close();
71 + db2.Close();
72 + return difference;
73 + }
74 +
75 + protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
76 + {
77 + bool difference = false;
78 +
79 + SummaryInfo summInfo1 = db1.SummaryInfo;
80 + SummaryInfo summInfo2 = db2.SummaryInfo;
81 + if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; }
82 + if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; }
83 + if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; }
84 + if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; }
85 + if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; }
86 + if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; }
87 + if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; }
88 + if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; }
89 + if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; }
90 + if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; }
91 + if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; }
92 + if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; }
93 + if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; }
94 + if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; }
95 + if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; }
96 + if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; }
97 + if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; }
98 + summInfo1.Close();
99 + summInfo2.Close();
100 +
101 + return difference;
102 + }
103 +
104 + protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
105 + {
106 + bool difference = false;
107 +
108 + string tempFile = Path.GetTempFileName();
109 + if(db2.GenerateTransform(db1, tempFile))
110 + {
111 + difference = true;
112 +
113 + Database db = db1;
114 + db.ViewTransform(tempFile);
115 +
116 + string row, column, change;
117 + using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " +
118 + "FROM `_TransformView` ORDER BY `Table`, `Row`"))
119 + {
120 + view.Execute();
121 +
122 + foreach (Record rec in view) using (rec)
123 + {
124 + column = String.Format("{0} {1}", rec[1], rec[2]);
125 + change = "";
126 + if (rec.IsNull(3))
127 + {
128 + row = "<DDL>";
129 + if (!rec.IsNull(4))
130 + {
131 + change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4));
132 + }
133 + }
134 + else
135 + {
136 + row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]";
137 + if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE")
138 + {
139 + column = String.Format("{0}.{1}", rec[1], rec[2]);
140 + change = "{" + rec[5] + "}->{" + rec[4] + "}";
141 + }
142 + }
143 +
144 + diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change);
145 + }
146 + }
147 + }
148 + File.Delete(tempFile);
149 +
150 + return difference;
151 + }
152 +
153 + private string DecodeColDef(int colDef)
154 + {
155 + const int icdLong = 0x0000;
156 + const int icdShort = 0x0400;
157 + const int icdObject = 0x0800;
158 + const int icdString = 0x0C00;
159 + const int icdTypeMask = 0x0F00;
160 + const int icdNullable = 0x1000;
161 + const int icdPrimaryKey = 0x2000;
162 +
163 + string def = "";
164 + switch(colDef & (icdTypeMask))
165 + {
166 + case icdLong : def = "LONG"; break;
167 + case icdShort : def = "SHORT"; break;
168 + case icdObject: def = "OBJECT"; break;
169 + case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break;
170 + }
171 + if((colDef & icdNullable) != 0)
172 + {
173 + def = def + " NOT NULL";
174 + }
175 + if((colDef & icdPrimaryKey) != 0)
176 + {
177 + def = def + " PRIMARY KEY";
178 + }
179 + return def;
180 + }
181 +
182 + protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
183 + {
184 + bool difference = false;
185 +
186 + IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
187 + IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
188 + string[] streams1 = new string[streams1List.Count];
189 + string[] streams2 = new string[streams2List.Count];
190 + streams1List.CopyTo(streams1, 0);
191 + streams2List.CopyTo(streams2, 0);
192 +
193 + IComparer caseInsComp = CaseInsensitiveComparer.Default;
194 + Array.Sort(streams1, caseInsComp);
195 + Array.Sort(streams2, caseInsComp);
196 +
197 + for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; )
198 + {
199 + int comp;
200 + if (i1 == streams1.Length)
201 + {
202 + comp = 1;
203 + }
204 + else if (i2 == streams2.Length)
205 + {
206 + comp = -1;
207 + }
208 + else
209 + {
210 + comp = caseInsComp.Compare(streams1[i1], streams2[i2]);
211 + }
212 + if(comp < 0)
213 + {
214 + diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]);
215 + i1++;
216 + difference = true;
217 + }
218 + else if(comp > 0)
219 + {
220 + diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]);
221 + i2++;
222 + difference = true;
223 + }
224 + else
225 + {
226 + if(streams1[i1] != ("" + ((char)5) + "SummaryInformation"))
227 + {
228 + string tempFile1 = Path.GetTempFileName();
229 + string tempFile2 = Path.GetTempFileName();
230 +
231 + using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1])))
232 + {
233 + view.Execute();
234 +
235 + using (Record rec = view.Fetch())
236 + {
237 + rec.GetStream(1, tempFile1);
238 + }
239 + }
240 +
241 + using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2])))
242 + {
243 + view.Execute();
244 +
245 + using (Record rec = view.Fetch())
246 + {
247 + rec.GetStream(1, tempFile2);
248 + }
249 + }
250 +
251 + IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
252 + StringWriter sw = new StringWriter();
253 + if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
254 + {
255 + diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]);
256 + diffOutput.Write(sw.ToString());
257 + difference = true;
258 + }
259 +
260 + File.Delete(tempFile1);
261 + File.Delete(tempFile2);
262 + }
263 + i1++;
264 + i2++;
265 + }
266 + }
267 +
268 + return difference;
269 + }
270 +
271 + public virtual IDiffEngine Clone()
272 + {
273 + return new MsiDiffEngine();
274 + }
275 + }
276 +}
src/samples/Dtf/DDiff/MspDiffEngine.cs new
+127
@@ -0,0 +1,127 @@
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 +using System;
4 +using System.IO;
5 +using WixToolset.Dtf.WindowsInstaller;
6 +using WixToolset.Dtf.WindowsInstaller.Package;
7 +
8 +namespace WixToolset.Dtf.Samples.DDiff
9 +{
10 + public class MspDiffEngine : MsiDiffEngine
11 + {
12 + public MspDiffEngine()
13 + {
14 + }
15 +
16 + private string GetPatchTargetOption(string[] options)
17 + {
18 + for(int i = 0; i < options.Length - 1; i++)
19 + {
20 + switch(options[i].ToLower())
21 + {
22 + case "/p": goto case "-patchtarget";
23 + case "-p": goto case "-patchtarget";
24 + case "/patchtarget": goto case "-patchtarget";
25 + case "-patchtarget": return options[i+1];
26 + }
27 + }
28 + return null;
29 + }
30 +
31 + public override float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
32 + {
33 + if(diffInput1 != null && File.Exists(diffInput1) &&
34 + diffInput2 != null && File.Exists(diffInput2) &&
35 + GetPatchTargetOption(options) != null &&
36 + (IsMspPatch(diffInput1) && IsMspPatch(diffInput2)))
37 + {
38 + return .80f;
39 + }
40 + else if(diffInput1 != null && File.Exists(diffInput1) &&
41 + diffInput2 != null && File.Exists(diffInput2) &&
42 + GetPatchTargetOption(options) == null &&
43 + (IsMspPatch(diffInput1) && IsMsiDatabase(diffInput2)) ||
44 + (IsMsiDatabase(diffInput1) && IsMspPatch(diffInput2)))
45 + {
46 + return .75f;
47 + }
48 + else
49 + {
50 + return 0;
51 + }
52 + }
53 +
54 + public override bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
55 + {
56 + bool difference = false;
57 +
58 + InstallPackage db1, db2;
59 + if(IsMspPatch(diffInput1))
60 + {
61 + string patchTargetDbFile = GetPatchTargetOption(options);
62 + if(patchTargetDbFile == null) patchTargetDbFile = diffInput2;
63 + string tempPatchedDbFile = Path.GetTempFileName();
64 + File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
65 + File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
66 + db1 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
67 + db1.ApplyPatch(new PatchPackage(diffInput1), null);
68 + db1.Commit();
69 + }
70 + else
71 + {
72 + db1 = new InstallPackage(diffInput1, DatabaseOpenMode.ReadOnly);
73 + }
74 + if(IsMspPatch(diffInput2))
75 + {
76 + string patchTargetDbFile = GetPatchTargetOption(options);
77 + if(patchTargetDbFile == null) patchTargetDbFile = diffInput1;
78 + string tempPatchedDbFile = Path.GetTempFileName();
79 + File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
80 + File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
81 + db2 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
82 + db2.ApplyPatch(new PatchPackage(diffInput2), null);
83 + db2.Commit();
84 + }
85 + else
86 + {
87 + db2 = new InstallPackage(diffInput2, DatabaseOpenMode.ReadOnly);
88 + }
89 +
90 + if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
91 + if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
92 + if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
93 +
94 + db1.Close();
95 + db2.Close();
96 +
97 + try
98 + {
99 + if(IsMspPatch(diffInput1)) File.Delete(db1.FilePath);
100 + if(IsMspPatch(diffInput1)) File.Delete(db2.FilePath);
101 + }
102 + catch(IOException)
103 + {
104 + #if DEBUG
105 + Console.WriteLine("Could not delete temporary files {0} and {1}", db1.FilePath, db2.FilePath);
106 + #endif
107 + }
108 +
109 + if(IsMspPatch(diffInput1) && IsMspPatch(diffInput2))
110 + {
111 + Database dbp1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
112 + Database dbp2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
113 +
114 + if(GetStreamsDiff(dbp1, dbp2, options, diffOutput, linePrefix, diffFactory)) difference = true;
115 + dbp1.Close();
116 + dbp2.Close();
117 + }
118 +
119 + return difference;
120 + }
121 +
122 + public override IDiffEngine Clone()
123 + {
124 + return new MspDiffEngine();
125 + }
126 + }
127 +}
src/samples/Dtf/DDiff/TextFileDiffEngine.cs new
+83
@@ -0,0 +1,83 @@
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 +using System;
4 +using System.IO;
5 +using System.Diagnostics;
6 +
7 +namespace WixToolset.Dtf.Samples.DDiff
8 +{
9 + public class TextFileDiffEngine : IDiffEngine
10 + {
11 + public TextFileDiffEngine()
12 + {
13 + }
14 +
15 + private bool IsTextFile(string file)
16 + {
17 + // Guess whether this is a text file by reading the first few bytes and checking for non-ascii chars.
18 +
19 + bool isText = true;
20 + FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
21 + byte[] buf = new byte[256];
22 + int count = stream.Read(buf, 0, buf.Length);
23 + for(int i = 0; i < count; i++)
24 + {
25 + if((buf[i] & 0x80) != 0)
26 + {
27 + isText = false;
28 + break;
29 + }
30 + }
31 + stream.Close();
32 + return isText;
33 + }
34 +
35 + public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
36 + {
37 + if(diffInput1 != null && File.Exists(diffInput1) &&
38 + diffInput2 != null && File.Exists(diffInput2) &&
39 + (IsTextFile(diffInput1) && IsTextFile(diffInput2)))
40 + {
41 + return .70f;
42 + }
43 + else
44 + {
45 + return 0;
46 + }
47 + }
48 +
49 + public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
50 + {
51 + try
52 + {
53 + bool difference = false;
54 + ProcessStartInfo psi = new ProcessStartInfo("diff.exe");
55 + psi.Arguments = String.Format("\"{0}\" \"{1}\"", diffInput1, diffInput2);
56 + psi.WorkingDirectory = null;
57 + psi.UseShellExecute = false;
58 + psi.WindowStyle = ProcessWindowStyle.Hidden;
59 + psi.RedirectStandardOutput = true;
60 + Process proc = Process.Start(psi);
61 +
62 + string line;
63 + while((line = proc.StandardOutput.ReadLine()) != null)
64 + {
65 + diffOutput.WriteLine("{0}{1}", linePrefix, line);
66 + difference = true;
67 + }
68 +
69 + proc.WaitForExit();
70 + return difference;
71 + }
72 + catch(System.ComponentModel.Win32Exception) // If diff.exe is not found, just compare the bytes
73 + {
74 + return new FileDiffEngine().GetDiff(diffInput1, diffInput2, options, diffOutput, linePrefix, diffFactory);
75 + }
76 + }
77 +
78 + public IDiffEngine Clone()
79 + {
80 + return new TextFileDiffEngine();
81 + }
82 + }
83 +}
src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs new
+90
@@ -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 +
3 +using System;
4 +using System.IO;
5 +using WixToolset.Dtf.WindowsInstaller;
6 +
7 +namespace WixToolset.Dtf.Samples.DDiff
8 +{
9 + public class VersionedFileDiffEngine : IDiffEngine
10 + {
11 + public VersionedFileDiffEngine()
12 + {
13 + }
14 +
15 + private bool IsVersionedFile(string file)
16 + {
17 + return Installer.GetFileVersion(file) != "";
18 + }
19 +
20 + public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
21 + {
22 + if(diffInput1 != null && File.Exists(diffInput1) &&
23 + diffInput2 != null && File.Exists(diffInput2) &&
24 + (IsVersionedFile(diffInput1) || IsVersionedFile(diffInput2)))
25 + {
26 + return .20f;
27 + }
28 + else
29 + {
30 + return 0;
31 + }
32 + }
33 +
34 + public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
35 + {
36 + bool difference = false;
37 +
38 + string ver1 = Installer.GetFileVersion(diffInput1);
39 + string ver2 = Installer.GetFileVersion(diffInput2);
40 +
41 + if(ver1 != ver2)
42 + {
43 + diffOutput.WriteLine("{0}File version: {1} -> {2}", linePrefix, ver1, ver2);
44 + difference = true;
45 + }
46 + else
47 + {
48 + FileStream stream1 = new FileStream(diffInput1, FileMode.Open, FileAccess.Read, FileShare.Read);
49 + FileStream stream2 = new FileStream(diffInput2, FileMode.Open, FileAccess.Read, FileShare.Read);
50 +
51 + byte[] buf1 = new byte[512];
52 + byte[] buf2 = new byte[512];
53 +
54 + while(!difference)
55 + {
56 + int count1 = stream1.Read(buf1, 0, buf1.Length);
57 + int count2 = stream2.Read(buf2, 0, buf2.Length);
58 +
59 + for(int i = 0; i < count1; i++)
60 + {
61 + if(i == count2 || buf1[i] != buf2[i])
62 + {
63 + difference = true;
64 + break;
65 + }
66 + }
67 + if(count1 < buf1.Length) // EOF
68 + {
69 + break;
70 + }
71 + }
72 +
73 + stream1.Close();
74 + stream2.Close();
75 +
76 + if(difference)
77 + {
78 + diffOutput.WriteLine("{0}File versions match but bits differ.", linePrefix);
79 + }
80 + }
81 +
82 + return difference;
83 + }
84 +
85 + public IDiffEngine Clone()
86 + {
87 + return new VersionedFileDiffEngine();
88 + }
89 + }
90 +}
src/samples/Dtf/Documents/Guide/Content/about.htm new
+59
@@ -0,0 +1,59 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Deployment Tools Foundation Overview</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Deployment Tools Foundation</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet"><span class="nolink">Overview</span></span>
15 + <span id="languageFilter">v4.0</span>
16 + </div>
17 + </div>
18 + <div id="main">
19 + <div id="header">
20 + </div>
21 + <div class="summary">
22 + <p>Deployment Tools Foundation is a rich set of .NET class libraries and
23 + related resources that together bring the Windows deployment platform
24 + technologies into the .NET world. It is designed to greatly simplify
25 + deployment-related development tasks while still exposing the complete
26 + functionality of the underlying technology.</p>
27 +
28 + <p>The primary focus of DTF is to provide a foundation for development of
29 + various kinds of tools to support deployment throughout the product
30 + lifecycle, including setup authoring, building, analysis, debugging, and
31 + testing tools. In addition to tools, DTF can also be useful for install-time
32 + activities such as setup bootstrappers, external UI, and custom actions,
33 + and for application run-time activities that need to access the deployment
34 + platform.</p>
35 +
36 + <p>For a description of the the latest changes, see <a
37 + href="whatsnew.htm">What's New</a>.</p>
38 +
39 + </div>
40 +
41 + <div id="footer">
42 + <p />
43 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
44 + wix-users@lists.sourceforge.net</a>
45 +
46 + <script type="text/javascript">
47 + var HT_mailLink = document.getElementById("HT_MailLink");
48 + var HT_mailLinkText = HT_mailLink.innerHTML;
49 + HT_mailLink.href += ": " + document.title;
50 + HT_mailLink.innerHTML = HT_mailLinkText;
51 + </script>
52 +
53 + <p />
54 +
55 + </div>
56 + </div>
57 +
58 +</body>
59 +</html>
src/samples/Dtf/Documents/Guide/Content/buildingcas.htm new
+94
@@ -0,0 +1,94 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Building Managed Custom Actions</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Building Managed Custom Actions</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <span class="nolink">Building</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 +
26 + <p>The build process for managed CA DLLs is a little complicated becuase of the
27 + proxy-wrapper and dll-export requirements. Here's an overview:</p>
28 + <ol>
29 + <li>
30 + <p>Compile your CA assembly, which references WixToolset.Dtf.WindowsInstaller.dll and
31 + marks exported custom actions with a CustomActionAttribute.</p>
32 + <li>
33 + <p>Package the CA assembly, CustomAction.config, WixToolset.Dtf.WindowsInstaller.dll,
34 + and any other dependencies using <b>MakeSfxCA.exe</b>. The filenames of CustomAction.config
35 + and WixToolset.Dtf.WindowsInstaller.dll must not be changed, since
36 + the custom action proxy specifically looks for those files.</p>
37 + </ol>
38 + <p><br>
39 + </p>
40 + <p><b>Compiling</b></p>
41 + <pre><font face="Consolas, Courier New">
42 + csc.exe
43 + /target:library
44 + /r:$(DTFbin)\WixToolset.Dtf.WindowsInstaller.dll
45 + /out:SampleCAs.dll
46 + *.cs
47 + </font></pre>
48 + <p><b>Wrapping</b><pre><font face="Consolas, Courier New">
49 + MakeSfxCA.exe
50 + $(OutDir)\SampleCAsPackage.dll
51 + $(DTFbin)\SfxCA.dll
52 + SampleCAs.dll
53 + CustomAction.config
54 + $(DTFbin)\WixToolset.Dtf.WindowsInstaller.dll
55 + </font></pre>
56 + </p>
57 + <p>Now the resulting package, SampleCAsPackage.dll, is ready to be inserted
58 + into the Binary table of the MSI.</p>
59 + <p><br/>
60 + </p>
61 + <p>For a working example of building a managed custom action package
62 + you can look at included sample ManagedCAs project.</p>
63 + <p><br/>
64 + </p>
65 +
66 + <p><br/></p>
67 + <p><b>See also:</b></p>
68 + <ul>
69 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
70 + <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
71 + </ul>
72 + <p><br/></p>
73 +
74 + </div>
75 +
76 + <div id="footer">
77 + <p />
78 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
79 + wix-users@lists.sourceforge.net</a>
80 +
81 + <script type="text/javascript">
82 + var HT_mailLink = document.getElementById("HT_MailLink");
83 + var HT_mailLinkText = HT_mailLink.innerHTML;
84 + HT_mailLink.href += ": " + document.title;
85 + HT_mailLink.innerHTML = HT_mailLinkText;
86 + </script>
87 +
88 + <p />
89 +
90 + </div>
91 + </div>
92 +
93 +</body>
94 +</html>
src/samples/Dtf/Documents/Guide/Content/cabpack.htm new
+63
@@ -0,0 +1,63 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Archive Pack/Unpack Tool</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Archive Pack/Unpack Tool</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="samples.htm">Samples</a> &gt;
17 + <span class="nolink">XPack</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p><pre><font face="Consolas, Courier New">Usage: CabPack.exe &lt;directory&gt; &lt;package.cab&gt;
26 +Usage: XPack /P &lt;archive.cab&gt; &lt;directory&gt;
27 +Usage: XPack /P &lt;archive.zip&gt; &lt;directory&gt;
28 +
29 +Packs all files in a directory tree into an archive,
30 +using either the cab or zip format. Any existing archive
31 +with the same name will be overwritten.
32 +
33 +
34 +Usage: XPack /U &lt;archive.cab&gt; &lt;directory&gt;
35 +Usage: XPack /U &lt;archive.zip&gt; &lt;directory&gt;
36 +
37 +Unpacks all files from a cab or zip archive to the
38 +specified directory. Any existing files with the same
39 +names will be overwritten.</font></pre>
40 + </p>
41 + <p><br/></p>
42 +
43 + </div>
44 +
45 + <div id="footer">
46 + <p />
47 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
48 + wix-users@lists.sourceforge.net</a>
49 +
50 + <script type="text/javascript">
51 + var HT_mailLink = document.getElementById("HT_MailLink");
52 + var HT_mailLinkText = HT_mailLink.innerHTML;
53 + HT_mailLink.href += ": " + document.title;
54 + HT_mailLink.innerHTML = HT_mailLinkText;
55 + </script>
56 +
57 + <p />
58 +
59 + </div>
60 + </div>
61 +
62 +</body>
63 +</html>
src/samples/Dtf/Documents/Guide/Content/cabs.htm new
+101
@@ -0,0 +1,101 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Working with Cabinet Files</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Working with Cabinet Files</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <span class="nolink">Cabinet Files</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 + <h3>Creating a cabinet</h3>
26 + <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
27 + cabInfo.Pack(<font color="purple">"D:\\FilesToCompress"</font>);</font></pre><br />
28 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the (future) location of the .cab file.</p>
29 + <p>2.&nbsp; Compress files:</p><ul>
30 + <li>Easily compress an entire directory with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_Pack.htm">Pack</a> method.</li>
31 + <li>Compress a specific list of exernal and internal filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_PackFiles.htm">PackFiles</a> method.</li>
32 + <li>Compress a dictionary mapping of internal to external filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_PackFileSet.htm">PackFileSet</a> method.</li>
33 + </ul>
34 +
35 + <p><br/></p>
36 + <h3>Listing a cabinet</h3>
37 + <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
38 + <font color="blue">foreach</font> (CabFileInfo fileInfo <font color="blue">in</font> cabInfo.GetFiles())
39 + Console.WriteLine(fileInfo.Name + <font color="purple">"\t"</font> + fileInfo.Length);</font></pre><br />
40 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the location of the .cab file.</p>
41 + <p>2.&nbsp; Enumerate files returned by the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_Cab_CabInfo_GetFiles.htm">GetFiles</a> method.</p><ul>
42 + <li>Each <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabFileInfo.htm">CabFileInfo</a> instance contains metadata about one file.</li>
43 + </ul>
44 +
45 + <p><br/></p>
46 + <h3>Extracting a cabinet</h3>
47 + <pre><font face="Consolas, Courier New"> CabInfo cabInfo = <font color="blue">new</font> CabInfo(<font color="purple">"package.cab"</font>);
48 + cabInfo.Unpack(<font color="purple">"D:\\ExtractedFiles"</font>);</font></pre><br />
49 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_Cab_CabInfo__ctor_1.htm">new CabInfo</a> instance referring to the location of the .cab file.</p>
50 + <p>2.&nbsp; Extract files:</p><ul>
51 + <li>Easily extract all files to a directory with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_Unpack.htm">Unpack</a> method.</li>
52 + <li>Easily extract a single file with the <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFile.htm">UnpackFile</a> method.</li>
53 + <li>Extract a specific list of filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFiles.htm">UnpackFiles</a> method.</li>
54 + <li>Extract a dictionary mapping of internal to external filenames with the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_Compression_ArchiveInfo_UnpackFileSet.htm">UnpackFileSet</a> method.</li>
55 + </ul>
56 +
57 + <p><br/></p>
58 + <h3>Getting progress</h3>
59 + Most cabinet operation methods have an overload that allows you to specify a event handler
60 + for receiving <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_ArchiveProgressEventArgs.htm">archive
61 + progress events</a>. The <a href="cabpack.htm">XPack sample</a>
62 + demonstrates use of the callback to report detailed progress to the console.
63 +
64 + <p><br/></p>
65 + <h3>Stream-based compression</h3>
66 + The CabEngine class contains static methods for performing compression/decompression operations directly
67 + on any kind of Stream. However these methods are more difficult to use, since the caller must implement a
68 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_ArchiveFileStreamContext.htm">stream context</a>
69 + that provides the file metadata which would otherwise have been provided by the filesystem. The CabInfo class
70 + uses the CabEngine class with FileStreams to provide the more traditional file-based interface.
71 +
72 + <p><br/></p>
73 + <p><b>See also:</b></p>
74 + <ul>
75 + <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabInfo.htm">CabInfo class</a></li>
76 + <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_Compression_Cab_CabEngine.htm">CabEngine class</a></li>
77 + <li><a href="cabpack.htm">XPack Sample Tool</a></li>
78 + </ul>
79 + <p><br/></p>
80 +
81 + </div>
82 +
83 + <div id="footer">
84 + <p />
85 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
86 + wix-users@lists.sourceforge.net</a>
87 +
88 + <script type="text/javascript">
89 + var HT_mailLink = document.getElementById("HT_MailLink");
90 + var HT_mailLinkText = HT_mailLink.innerHTML;
91 + HT_mailLink.href += ": " + document.title;
92 + HT_mailLink.innerHTML = HT_mailLinkText;
93 + </script>
94 +
95 + <p />
96 +
97 + </div>
98 + </div>
99 +
100 +</body>
101 +</html>
src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm new
+63
@@ -0,0 +1,63 @@
1 +<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2 +<html>
3 + <head>
4 + <title>Managed Wrapper Library for Cabinet APIs</title>
5 + <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 + <link rel="stylesheet" type="text/css" href="MSDN.css">
7 + </head>
8 + <body id="bodyID" class="dtBODY">
9 + <div id="nsbanner">
10 + <div id="bannerrow1">
11 + <table class="bannerparthead" cellspacing="0" id="Table1">
12 + <tr id="hdr">
13 + <td class="runninghead">Managed Libraries for Windows Installer</td>
14 + <td class="product"></td>
15 + </tr>
16 + </table>
17 + </div>
18 + <div id="TitleRow">
19 + <h1 class="dtH1">Managed Wrapper Library for Cabinet APIs</h1>
20 + </div>
21 + </div>
22 + <div id="nstext">
23 + <p>This is a managed library that provides the ability to
24 + create and extract cabinet files. It uses cabinet.dll (present on all versions of Windows)
25 + to do the actual compression/decompression. It provides access to nearly all
26 + cabinet capabilities, including spanning of multiple cab files. It even has support for
27 + preserving directory structures and UTF8 paths.</p>
28 + <p>There are two ways to use the library. <a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetInfo.html">CabinetInfo</a>
29 + and <a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetFileInfo.html">CabinetFileInfo</a>
30 + (similar to DirectoryInfo and FileInfo respectively)
31 + provide high-level object-oriented methods for doing common file-based cabinet creation and
32 + extraction tasks. On the other hand, the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.Cabinet.html">Cabinet</a>
33 + class provides low-level access to all
34 + functionality, and operates completely in terms of .NET Streams. The previous two classes use
35 + the Cabinet class to do all the actual work.</p>
36 + <p>There are also two ways to build the library.
37 + Compiling it normally will produce the fully functional
38 + library in the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.html">Microsoft.Cab</a>
39 + namespace, while compiling it with the <tt>/D:CABMINIMAL
40 + /D:CABEXTRACTONLY</tt> flags will create a compact assembly with only the core extraction
41 + functionality, in the <a href="ms-its:MMLRef.chm::/Microsoft.Cab.MiniExtract.html">Microsoft.Cab.MiniExtract</a>
42 + namespace.</p>
43 + <p>The cabinet library interops with native cabinet APIs which use the 'cdecl'
44 + calling-convention. When building against .NET Framework versions before 2.0,
45 + this library requires a special post-build step to process the UnmanagedFunctionPointerAttribute.
46 + If you use this code in another assembly, don't forget to run <a href="augmentil.htm">AugmentIL</a>
47 + on it to fix the delegate calling-conventions, otherwise you will encounter a
48 + NullReferenceException when attempting to call the cabinet APIs. When building against
49 + .NET Framework version 2.0 or later, the UnmanagedFunctionPointerAttribute.cs source file
50 + should be omitted.</p>
51 +
52 + <p><br/></p>
53 + <p><b>See also:</b></p>
54 + <ul>
55 + <li><a href="cabs.htm">Working with Cabinet Files</a></li>
56 + <li><a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetInfoMethods.html">CabinetInfo Methods</a></li>
57 + <li><a href="ms-its:MMLRef.chm::/Microsoft.Cab.CabinetMethods.html">Cabinet Methods</a></li>
58 + <li><a href="cabpack.htm">CabPack Sample Tool</a></li>
59 + </ul>
60 + <p><br/></p>
61 + </div>
62 + </body>
63 +</html>
src/samples/Dtf/Documents/Guide/Content/caconfig.htm new
+83
@@ -0,0 +1,83 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Specifying the Runtime Version</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Specifying the Runtime Version</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <a href="writingcas.htm">Writing CAs</a> &gt;
18 + <span class="nolink">CustomAction.config</span>
19 + </span>
20 + </div>
21 + </div>
22 + <div id="main">
23 + <div id="header">
24 + </div>
25 + <div class="summary">
26 +
27 + <p>Every managed custom action package should contain a CustomAction.config file, even though it is not required by the toolset.
28 + Here is a sample:</p><pre><font face="Consolas, Courier New">
29 +&lt;?xml version="1.0" encoding="utf-8" ?&gt;
30 +&lt;configuration&gt;
31 + &lt;startup&gt;
32 + &lt;supportedRuntime version=&quot;v2.0.50727&quot;/&gt;
33 + &lt;/startup&gt;
34 +&lt;/configuration&gt;</font></pre><br />
35 + <p>The configuration file follows the standard schema for .NET Framework
36 + configuration files <a target=_blank href="http://msdn2.microsoft.com/en-us/library/9w519wzk(VS.80).aspx">documented on MSDN</a>.</p>
37 + <p><br/></p>
38 + <p><b>Supported Runtime Version</b></p>
39 + <p>In the startup section, use <a target=_blank href="http://msdn2.microsoft.com/en-us/library/w4atty68(VS.80).aspx">supportedRuntime</a>
40 + tags to explicitly specify the version(s) of the .NET Framework that the custom action should run on.
41 + If no versions are specified, the chosen version of the .NET Framework will be
42 + the "best" match to what WixToolset.Dtf.WindowsInstaller.dll was built against.</p>
43 + <p><font color="red"><b>Warning: leaving the version unspecified is dangerous</b></font>
44 + as it introduces a risk of compatibility problems with future versions of the .NET Framework.
45 + It is highly recommended that you specify only the version(s)
46 + of the .NET Framework that you have tested against.</p>
47 + <p><br/></p>
48 +
49 + <p><b>Other Configuration</b></p>
50 + <p>Various other kinds of configuration settings may also be added to this file, as it is a standard
51 + <a target=_blank href="http://msdn2.microsoft.com/en-us/library/kza1yk3a(VS.80).aspx">.NET Framework application config file</a>
52 + for the custom action.</p>
53 + <p><br/></p>
54 +
55 + <p><b>See also:</b></p>
56 + <ul>
57 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
58 + <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
59 + <li><a href="caproxy.htm">Proxy for Managed Custom Actions</a></li>
60 + </ul>
61 + <p><br/></p>
62 +
63 + </div>
64 +
65 + <div id="footer">
66 + <p />
67 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
68 + wix-users@lists.sourceforge.net</a>
69 +
70 + <script type="text/javascript">
71 + var HT_mailLink = document.getElementById("HT_MailLink");
72 + var HT_mailLinkText = HT_mailLink.innerHTML;
73 + HT_mailLink.href += ": " + document.title;
74 + HT_mailLink.innerHTML = HT_mailLinkText;
75 + </script>
76 +
77 + <p />
78 +
79 + </div>
80 + </div>
81 +
82 +</body>
83 +</html>
src/samples/Dtf/Documents/Guide/Content/caproxy.htm new
+74
@@ -0,0 +1,74 @@
1 +<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2 +<html>
3 + <head>
4 + <title>Proxy Class for Managed Custom Actions</title>
5 + <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 + <link rel="stylesheet" type="text/css" href="MSDN.css">
7 + </head>
8 + <body id="bodyID" class="dtBODY">
9 + <div id="nsbanner">
10 + <div id="bannerrow1">
11 + <table class="bannerparthead" cellspacing="0" id="Table1">
12 + <tr id="hdr">
13 + <td class="runninghead">Managed Libraries for Windows Installer</td>
14 + <td class="product"></td>
15 + </tr>
16 + </table>
17 + </div>
18 + <div id="TitleRow">
19 + <h1 class="dtH1">Proxy for Managed Custom Actions</h1>
20 + </div>
21 + </div>
22 + <div id="nstext">
23 + <p>The custom action proxy allows an MSI developer to write
24 + custom actions in managed code, while maintaing all the advantages of type 1
25 + DLL custom actions including full access to installer state, properties,
26 + and the session database.</p>
27 + <p>There are generally four problems that needed to be
28 + solved in order to create a type 1 custom action in managed code:</p>
29 + <ol>
30 + <li><p><strong>Exporting the CA function as a native entry point callable by
31 + MSI:</strong> The Windows Installer engine expects to call a LoadLibrary and
32 + GetProcAddress on the custom action DLL, so an unmanaged DLL needs to implement
33 + the function that is initially called by MSI and ultimately returns the result.
34 + This function acts as a proxy to relay the custom action call into the
35 + managed custom action assembly, and relay the result back to the caller. </p>
36 + <li><strong>Providing supporting assemblies without
37 + requiring them to be installed as files:</strong> If a DLL custom
38 + action runs before the product's files are installed, then it is difficult
39 + to provide any supporting files, because of the way the CA DLL is singly
40 + extracted and executed from a temp file. (This can be a problem for
41 + unmanaged CAs as well.) With managed custom actions we have already hit
42 + that problem since both the CA assembly and the MSI wrapper assembly
43 + need to be loaded. To solve this, the proxy DLL carries an appended
44 + cab package. When invoked, it will extract all contents of the
45 + cab package to a temporary working directory. This way the cab package can
46 + carry any arbitrary dependencies the custom action may require.</li>
47 + <li><p><strong>Hosting and configuring the Common Language Runtime:</strong>
48 + In order to invoke a method in a managed assembly from a previously
49 + unmanaged process, the CLR needs to be "hosted". This involves choosing
50 + the correct version of the .NET Framework to use out of the available
51 + version(s) on the system, binding that version to the current process, and
52 + configuring it to load assemblies from the temporary working directory.</p>
53 + <li><p><strong>Converting the integer session handle into a
54 + Session object:</strong> The <a href="">Session</a> class in the managed
55 + wrapper library has a constructor which takes an integer session handle as
56 + its parameter. So the proxy simply instantiates this object before
57 + calling the real CA function.</p>
58 + </ol>
59 + <p>The unmanaged CAPack module, when used in combination with the managed proxy in
60 + the
61 + Microsoft.WindowsInstaller assembly, accomplishes the tasks above to enable
62 + fully-functional managed DLL custom actions.</p>
63 + <p><br/></p>
64 + <p><b>See also:</b></p>
65 + <ul>
66 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
67 + <li><a href="caconfig.htm">Writing the CustomAction.config file</a></li>
68 + <li><a href="samplecas.htm">Sample C# Custom Actions</a></li>
69 + <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
70 + </ul>
71 + <p><br/></p>
72 + </div>
73 + </body>
74 +</html>
src/samples/Dtf/Documents/Guide/Content/databases.htm new
+120
@@ -0,0 +1,120 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Working with MSI Databases</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Working with MSI Databases</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <span class="nolink">MSI Databases</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 + <h3>Querying a database</h3>
26 + <pre><font face="Consolas, Courier New"> <font color=blue>using</font> (Database db = <font color=blue>new</font> Database(<font color="purple">"product.msi"</font>, DatabaseOpenMode.ReadOnly))
27 + {
28 + <font color=blue>string</font> value = (<font color=blue>string</font>) db.ExecuteScalar(
29 + <font color="purple">"SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"</font>, propName);
30 + }</font></pre><br />
31 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database__ctor.htm">new Database</a>
32 + instance referring to the location of the .msi or .msm file.</p>
33 + <p>2.&nbsp; Execute the query:</p><ul>
34 + <li>The <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database_ExecuteScalar.htm">ExecuteScalar</a>
35 + method is a shortcut for opening a view, executing the view, and fetching a single value.</li>
36 + <li>The <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database_ExecuteQuery.htm">ExecuteQuery</a>
37 + method is a shortcut for opening a view, executing the view, and fetching all values.</li>
38 + <li>Or do it all manually with <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_OpenView.htm">Database.OpenView</a>,
39 + <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_View_Execute.htm">View.Execute</a>, and
40 + <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_View_Fetch.htm">View.Fetch</a>.</li>
41 + </ul>
42 +
43 + <p><br/></p>
44 + <h3>Updating a binary</h3>
45 + <pre><font face="Consolas, Courier New"> Database db = <font color=blue>null</font>;
46 + View view = <font color=blue>null</font>;
47 + Record rec = <font color=blue>null</font>;
48 + <font color=blue>try</font>
49 + {
50 + db = <font color=blue>new</font> Database(<font color="purple">"product.msi"</font>, DatabaseOpenMode.Direct);
51 + view = db.OpenView(<font color="purple">"UPDATE `Binary` SET `Data` = ? WHERE `Name` = '{0}'"</font>, binName))
52 + rec = <font color=blue>new</font> Record(1);
53 + rec.SetStream(1, binFile);
54 + view.Execute(rec);
55 + db.Commit();
56 + }
57 + <font color=blue>finally</font>
58 + {
59 + <font color=blue>if</font> (rec != <font color=blue>null</font>) rec.Close();
60 + <font color=blue>if</font> (view != <font color=blue>null</font>) view.Close();
61 + <font color=blue>if</font> (db != <font color=blue>null</font>) db.Close();
62 + }</font></pre><br />
63 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Database__ctor.htm">new Database</a>
64 + instance referring to the location of the .msi or .msm file.</p>
65 + <p>2.&nbsp; Open a view by calling one of the <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_OpenView.htm">Database.OpenView</a> overloads.</p><ul>
66 + <li>Parameters can be substituted in the SQL string using the String.Format syntax.</li>
67 + </ul>
68 + <p>3.&nbsp; Create a record with one field containing the new binary value.</p>
69 + <p>4.&nbsp; Execute the view by calling one of the <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_View_Execute.htm">View.Execute</a> overloads.</p><ul>
70 + <li>A record can be supplied for substitution of field tokens (?) in the query.</li>
71 + </ul>
72 + <p>5.&nbsp; <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_Commit.htm">Commit</a> the Database.</p>
73 + <p>6.&nbsp; <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_InstallerHandle_Close.htm">Close</a> the handles.</p>
74 +
75 + <p><br/></p>
76 + <h3>About handles</h3>
77 + <p>Handle objects (Database, View, Record, SummaryInfo, Session) will remain open until
78 + they are explicitly closed or until the objects are collected by the GC. So for the tightest
79 + code, handle objects should be explicitly closed when they are no longer needed,
80 + since closing them can release significant resources, and too many unnecessary
81 + open handles can degrade performance. This is especially important within a loop
82 + construct: for example when iterating over all the Records in a table, it is much cleaner
83 + and faster to close each Record after it is used.</p>
84 + <p>The handle classes in the managed library all extend the
85 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_InstallerHandle.htm">InstallerHandle</a>
86 + class, which implements the IDisposable interface. This makes them easily managed with C#'s
87 + using statement. Alternatively, they can be closed in a finally block.</p>
88 + <p>As a general rule, <i>methods</i> in the library return new handle objects that should be managed
89 + and closed by the calling code, while <i>properties</i> only return a reference to a prexisting handle
90 + object.</p>
91 +
92 + <p><br/></p>
93 + <p><b>See also:</b></p>
94 + <ul>
95 + <li><a href="powerdiff.htm">MSI Diff Sample Tool</a></li>
96 + <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Database.htm">Database Class</a></li>
97 + </ul>
98 + <p><br/></p>
99 +
100 + </div>
101 +
102 + <div id="footer">
103 + <p />
104 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
105 + wix-users@lists.sourceforge.net</a>
106 +
107 + <script type="text/javascript">
108 + var HT_mailLink = document.getElementById("HT_MailLink");
109 + var HT_mailLinkText = HT_mailLink.innerHTML;
110 + HT_mailLink.href += ": " + document.title;
111 + HT_mailLink.innerHTML = HT_mailLinkText;
112 + </script>
113 +
114 + <p />
115 +
116 + </div>
117 + </div>
118 +
119 +</body>
120 +</html>
src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm new
+66
@@ -0,0 +1,66 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Debugging Managed Custom Actions</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Debugging Managed Custom Actions</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <span class="nolink">Debugging</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p>There are two ways to attach a debugger to a managed custom action.</p>
26 + <p><b>Attach to message-box:</b> Add some temporary code to your custom action to display a
27 + message box. Then when the message box pops up at install time, you can attch your
28 + debugger to that process (usually identifiable by the title of the message box).
29 + Once attached, you can ensure that symbols are loaded if necessary (they will be automatically
30 + loaded if PDB files were embedded in the CA assembly at build time), then set breakpoints
31 + anywhere in the custom action code.</p>
32 + <p><b>MMsiBreak environment variable:</b> When debugging <i>managed</i> custom actions,
33 + you should use the MMsiBreak environment variable instead of MsiBreak. Set the MMsiBreak
34 + variable to the custom action entrypoint name. (Remember this might be different from
35 + the method name if it was overridden by the CustomActionAttribute.) When the CA proxy
36 + finds a matching name, the CLR JIT-debugging dialog
37 + will appear with text similar to "An exception 'Launch for user' has occurred
38 + in <i>YourCustomActionName</i>." The debug break occurs after the custom
39 + action assembly has been loaded, but just before custom action method is invoked.
40 + Once attached, you can ensure that symbols are loaded if necessary,
41 + then set breakpoints anywhere in the custom action code. Note: the MMsiBreak
42 + environment variable can also accept a comma-separated list of action names, any of
43 + which will cause a break when hit.</p>
44 + <p><br/></p>
45 +
46 + </div>
47 +
48 + <div id="footer">
49 + <p />
50 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
51 + wix-users@lists.sourceforge.net</a>
52 +
53 + <script type="text/javascript">
54 + var HT_mailLink = document.getElementById("HT_MailLink");
55 + var HT_mailLinkText = HT_mailLink.innerHTML;
56 + HT_mailLink.href += ": " + document.title;
57 + HT_mailLink.innerHTML = HT_mailLinkText;
58 + </script>
59 +
60 + <p />
61 +
62 + </div>
63 + </div>
64 +
65 +</body>
66 +</html>
src/samples/Dtf/Documents/Guide/Content/dependencies.htm new
+88
@@ -0,0 +1,88 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Dependencies</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Dependencies</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="about.htm">Overview</a> &gt;
16 + <span class="nolink">Dependencies</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 + <p>This page lists all the components that the DTF project depends on, at build time and at run-time.</p>
26 +
27 + <h3>Build-time Dependencies</h3>
28 + <ul>
29 + <li><p><b>Visual Studio / .NET Framework</b> - Most of DTF can be built with Visual Studio 2005 &
30 + .NET Framework 2.0. However, the LINQ project requires VS 2008 & .NET Framework 3.5.</p></li>
31 +
32 + <li><p><b>Sandcastle</b> - .NET documentation build engine from Microsoft, used to process all the XML doc-comments
33 + in DTF libraries into DTFAPI.chm.
34 + <a href="http://www.codeplex.com/Sandcastle/" target="_blank">(official site)</a></p></li>
35 +
36 + <li><p><b>Sandcastle Builder</b> - Sandcastle by itself is complex and difficult to use; this free tool
37 + from Codeplex provides an easy-to-use project system around it to automate the documentation build process.
38 + <a href="http://www.codeplex.com/SHFB/" target="_blank">(project link)</a></p></li>
39 +
40 + <li><p><b>HTML Help Workshop</b> - Tools for building HTML Help 1.x (CHM files). Used to build DTF.chm.
41 + <a href="http://msdn2.microsoft.com/en-us/library/ms669985.aspx" target="_blank">(download link)</a></p></li>
42 + </ul>
43 +
44 + <h3>Run-time Dependencies</h3>
45 + <ul>
46 + <li><p><b>.NET Framework</b> - Most of DTF requires .NET Framework 2.0. (.NET 1.1 is no longer supported.)
47 + The only exception is the LINQ assembly which requires .NET Framework 3.5.</p></li>
48 +
49 + <li><p><b>Windows Installer</b> - Windows Installer introduced new APIs and capabilities with each successive
50 + version. Obviously, the corresponding functionality in the managed APIs is only available when the required
51 + version of the Windows Instaler (msi.dll) is installed on the system. Use the Installer.Version property
52 + to easily check the currently installed MSI version. Attempting to use an API not supported by the current
53 + version will result in an EntryPointNotFoundException. To check what version is required for a particular API,
54 + see the documentation link to the corresponding unmanaged API in MSI.chm.</p>
55 + <p>In some instances when a newer version of MSI provides an "Ex" alternative to a function, only the "Ex"
56 + function is used by the managed library. This may hide some functionality that would have otherwise been
57 + available on a system with an older version of MSI.</p></li>
58 +
59 + <li><p><b>cabinet.dll</b> - The DTF cabinet compression library uses cabinet.dll to implement the
60 + low-level cabinet compression and decompression. This DLL is part of all versions of Windows,
61 + located in the system directory.</p></li>
62 +
63 + <li><p><b>System.IO.Compression.DeflateStream</b> - The DTF zip compression library uses this class
64 + to implement the low-level zip compression and decompression. This class is part of .NET Framework
65 + 2.0 and later.</p></li>
66 + </ul>
67 +
68 + </div>
69 +
70 + <div id="footer">
71 + <p />
72 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
73 + wix-users@lists.sourceforge.net</a>
74 +
75 + <script type="text/javascript">
76 + var HT_mailLink = document.getElementById("HT_MailLink");
77 + var HT_mailLinkText = HT_mailLink.innerHTML;
78 + HT_mailLink.href += ": " + document.title;
79 + HT_mailLink.innerHTML = HT_mailLinkText;
80 + </script>
81 +
82 + <p />
83 +
84 + </div>
85 + </div>
86 +
87 +</body>
88 +</html>
src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm new
+34
@@ -0,0 +1,34 @@
1 +<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2 +<html>
3 + <head>
4 + <title>Managed Wrapper for Binary File Patch APIs</title>
5 + <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 + <link rel="stylesheet" type="text/css" href="MSDN.css">
7 + </head>
8 + <body id="bodyID" class="dtBODY">
9 + <div id="nsbanner">
10 + <div id="bannerrow1">
11 + <table class="bannerparthead" cellspacing="0" id="Table1">
12 + <tr id="hdr">
13 + <td class="runninghead">Managed Libraries for Windows Installer</td>
14 + <td class="product"></td>
15 + </tr>
16 + </table>
17 + </div>
18 + <div id="TitleRow">
19 + <h1 class="dtH1">Managed Wrapper for Binary File Patch APIs</h1>
20 + </div>
21 + </div>
22 + <div id="nstext">
23 + <p>The binary file patch creation and application APIs (supplied by MsPatchC.dll and
24 + MsPatchA.dll) are wrapped in the Microsoft.WindowsInstaller.FilePatch.dll assembly.</p>
25 +
26 + <p><br/></p>
27 + <p><b>See also:</b></p>
28 + <ul>
29 + <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.FilePatch.html">FilePatch Class</a></li>
30 + </ul>
31 + <p><br/></p>
32 + </div>
33 + </body>
34 +</html>
src/samples/Dtf/Documents/Guide/Content/history.htm new
+437
@@ -0,0 +1,437 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Change History</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Change History</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="about.htm">Overview</a> &gt;
16 + <span class="nolink">Change History</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 +<h3><b>2007-07-03</b></h3>
26 +<i>See <a href="whatsnew.htm">What's New?</a></i><br />&nbsp;<br />&nbsp;<br />
27 +<hr size="2"/>
28 +<h3><b>2005-03-30</b></h3>
29 +
30 +<ul>
31 +<li>New custom action proxy<ul>
32 + <li><b>Managed custom actions use an XML config file to specify the CLR version.</b></li>
33 + <li>New CAPack module is an unmanaged self-extracting CA DLL that can wrap both
34 + managed and unmanaged custom actions. (The old managed CAProxy module is obsolete.)</li>
35 + <li>Custom action build process is different but still complicated --
36 + see documentation for details.</li>
37 + <li>CustomActionAttribute no longer accepts the optional NativeDependencies
38 + parameter since it does not apply to the new proxy (all packaged files
39 + are always extracted and available when the CA executes). </li>
40 +</ul></li>
41 +
42 +<li>64bit support<ul>
43 + <li>Various code fixes to pointer/handle types and structure alignments.</li>
44 + <li>Cabinet and MSI libraries tested on AMD64 CLR.</li>
45 + <li>Unmanaged and managed parts of custom action proxy tested on AMD64.</li>
46 +</ul></li>
47 +
48 +<li>MSI 3.1 APIs added:<ul>
49 + <li>Installer.SetExternalUI(ExternalUIRecordHandler)</li>
50 + <li>Installer.NotifySidChange</li>
51 +</ul></li>
52 +
53 +<li>Code builds easier with .NET Famework 2.0<ul>
54 + <li>AugmentIL post-build step is no longer necessary when compiling the cabinet code
55 + against .NET Framework 2.0, which has builtin support for cdecl delegates.</li>
56 + <li>All C# code compiles against .NET Framework 2.0 without obsolete warnings,
57 + when setting the NETFX2 preprocessor define.</li>
58 + <li>Same code is still compatible with .NET Framework 1.0 + AugmentIL.</li>
59 +</ul></li>
60 +
61 +<li>Miscellaneous bugfixes/changes:<ul>
62 + <li>InstallPackage.ExtractFiles could fail in some cominations of
63 + compressed/uncompressed files - fixed.</li>
64 + <li>Installer.DeterminePatchSequence was broken due to an incorrect interop struct - fixed.</li>
65 + <li>CabinetInfo and CabinetFileInfo classes made serializable.</li>
66 + <li>Added Session.FormatString method to simplify formatting a string with
67 + property substitutions.</li>
68 +</ul></li>
69 +<li>Documentation updates:<ul>
70 + <li>Updated all documentation for new CA proxy.</li>
71 + <li>Added new topic discussing InstallUtil.</li>
72 +</ul></li>
73 +</ul>
74 +
75 +<hr size="2"/>
76 +<h3><b>2004-04-13</b></h3>
77 +
78 +<ul>
79 +<li>Documentation<ul>
80 + <li>Consolidated all documentation into a single CHM file.</li>
81 + <li>Added new topics about working with MSI databases & cabinet files,
82 + to help new users get oriented more easily.</li>
83 +</ul></li>
84 +
85 +<li>WindowsInstaller<ul>
86 + <li>Removed [Beta] tags from MSI 3.0 APIs, but otherwise there
87 + have been no changes since 3.0 Beta 1.<ul>
88 + <li>Be warned these are still the least-tested parts of
89 + the library, so early users may encounter bugs.</li>
90 + </ul></li>
91 +</ul></li>
92 +
93 +<li>InstallPackage<ul>
94 + <li>Fixed InstallPackage.ExtractFiles() bug when directory doesn't exist.</li>
95 + <li>Added ability to handle uncompressed files in a package marked as compressed.</li>
96 +</ul></li>
97 +
98 +<li>Cabinet<ul>
99 + <li>Fixed improper handling of file attributes.<ul>
100 + <li>This bug caused some packages to not be extractable by other tools.</li>
101 + </ul></li>
102 + <li>Added support for UTF filenames.<ul>
103 + <li>Non-ASCII filenames will automatically be stored as UTF-8.
104 + (But note most other tools don't know how to extract them.)</li>
105 + </ul></li>
106 +</ul></li>
107 +</ul>
108 +
109 +<hr size="2"/>
110 +<h3><b>2003-10-13</b></h3>
111 +
112 +<ul>
113 +<li>Cab<ul>
114 + <li>Fixed a bug introduced in v2.4.0 that caused files to be left in the %TEMP%
115 + directory after creating a cab.</li>
116 + <li>Unsealed the CabinetInfo, CabinetFileInfo, CabinetStatus classes and made a few methods
117 + protected and virtual.</li>
118 +</ul></li>
119 +
120 +<li>AugmentIL<ul>
121 + <li>Fixed a bug that sometimes caused a crash when specifying a relative output path
122 + on the command-line.</li>
123 + <li>Fixed a bug that sometimes caused the Win32 version to be missing from the output file.</li>
124 +</ul></li>
125 +
126 +<li>Samples\Diff: added new sample tool<ul>
127 + <li>Recursively diffs directories, MSIs, MSPs, CABs, other files.</li>
128 +</ul></li>
129 +</ul>
130 +
131 +<hr size="2"/>
132 +<h3><b>2003-09-23</b></h3>
133 +
134 +<ul>
135 +<li>Cab<ul>
136 + <li>Fixed a bug that caused compressing very large files/file sets to use way too
137 + much memory. Performance on large inputs is now within a few % of native cab tools
138 + (sometimes even a little faster!) for the same compression level.</li>
139 +</ul></li>
140 +
141 +<li>WindowsInstaller<ul>
142 + <li>All the new MSI 3.0 beta APIs are wrapped, resulting in the following additions:<ul>
143 + <li>New classes - Product, Patch (for accessing sourcelist and other config)</li>
144 + <li>New methods on Install class - GetProducts, GetPatches, RemovePatches,
145 + ApplyMultiplePatches, DetermineApplicablePatches, ExtractPatchXmlData</li>
146 + <li>New enumerations - InstallContext, PatchStates, SourceType</li>
147 + <li>Additional InstallProperty values</li>
148 + </ul></li>
149 + <li>Note, MSI 3.0 support should be considered preliminary for now,
150 + as APIs (both native and managed) are subject to change.</li>
151 + <li>For MSI 2.0 compatibility, developers should not use any classes or
152 + methods that are marked as [MSI 3.0 beta] in the documentation.</li>
153 + <li>And unrelated to 3.0, a few additional enums have been added:
154 + DialogAttributes, ControlAttributes, CustomActionTypes,
155 + IniFileAction, RegistryRoot, RemoveFileInstallMode,
156 + ServiceControlEvents, ServiceInstallFlags, TextStyles,
157 + UpgradeAttributes, LocatorType</li>
158 + <li>Also made a few minor non-breaking changes to keep the library FxCop-clean.</li>
159 +</ul></li>
160 +
161 +<li>AugmentIL<ul>
162 + <li>Added support for strongname signing and delay-signing. AugmentIL tries to
163 + locate the keyfile using the AssemblyKeyFileAttribute, or you may specify the
164 + path with the new /key option.</li>
165 + <li>All &quot;released&quot; assemblies will now be strongname-signed
166 + (with an unofficial key).</li>
167 +</ul></li>
168 +
169 +<li>CAProxy<ul>
170 + <li>Added support for NativeDependencies property on CustomActionAttribute. This enables
171 + custom actions to P/Invoke into native DLLs that are carried with them.</li>
172 +</ul></li>
173 +
174 +<li>Samples\SampleCAs<ul>
175 + <li>In SampleCA2, changed MessageBox.Show(&quot;&quot;) to session.Message(User,&quot;&quot;),
176 + because generally it is a bad practice for CAs to show independent UI.</li>
177 + <li>Added test of CustomActionAttribute.NativeDependencies functionality.</li>
178 +</ul></li>
179 +
180 +<li>Samples\CabPack: added new sample<ul>
181 + <li>Demonstrates &amp; tests the cab library by creating self-extracting packages</li>
182 +</ul></li>
183 +
184 +<li>Samples\Inventory: added new sample<ul>
185 + <li>Shows a hierarchical, relational, searchable view of all of the product,
186 + feature, component, file, and patch data managed by MSI, for all products
187 + installed on the system.</li>
188 +</ul></li>
189 +</ul>
190 +
191 +<hr size="2"/>
192 +<h3><b>2003-09-12</b></h3>
193 +
194 +<ul>
195 +<li>Cab:<ul>
196 + <li>Added CabinetInfo.CompressDirectory method, capable of compressing an
197 + entire directory tree structure.</li>
198 + <li>Updated documentation of various methods concerning support of directory
199 + structure inside cabs.</li>
200 + <li>CabinetInfo case-sensitivity was inconsistent -
201 + now it is case-insensitive by default, though case is still preserved</li>
202 + <li>Separated assembly attributes into assembly.cs</li>
203 +</ul></li>
204 +<li>Msi:<ul>
205 + <li>InstallerException and subclasses automatically get extended error data
206 + from MSI's last-error-record when available. The data is stored
207 + in the exception and made available through the GetErrorRecord()
208 + method, and the exception's Message includes the formatted error
209 + message and data. This makes most exceptions extremely informative!</li>
210 + <li>Added View.GetValidationErrors() method, and supporting ValidationErrorInfo
211 + struct and ValidationError enum. This wrapper for the MsiViewGetError
212 + API had been accidentally left out.</li>
213 + <li>Session.Message() now supports message-box flags to specify buttons &amp; icon</li>
214 + <li>Added doc remarks to various methods about closing handles.</li>
215 + <li>Separated assembly attributes into assembly.cs</li>
216 +</ul></li>
217 +<li>AugmentIL:<ul>
218 + <li>Recent builds of ildasm v2.0.* have a slightly different output format,
219 + which could break AugmentIL in some cases - fixed</li>
220 +</ul></li>
221 +<li>SampleCAs:<ul>
222 + <li>Removed 'using' clause from SampleCA1 -- there's no need to close the session's active database handle</li>
223 +</ul></li>
224 +<li>Documentation:<ul>
225 + <li>Added note to ReadMe about compiling the cab source into another assembly</li>
226 +</ul></li>
227 +</ul>
228 +
229 +<hr size="2"/>
230 +<h3><b>2003-08-07</b></h3>
231 +
232 +<ul>
233 +<li>Cab:<ul>
234 + <li>CabinetInfo.IsValid() usually returned false even for valid cabs - fixed</li>
235 + <li>Extracting cab files with null timestamps generated exception - fixed</li>
236 +</ul></li>
237 +<li>Msi:<ul>
238 + <li>Added InstallCanceledException, subclass of InstallerException;
239 + Methods which may be canceled by the user can throw this exception</li>
240 + <li>Added MessageResult enumeration;
241 + Used by Session.Message() and ExternalUIHandler delegate</li>
242 + <li>Installer.EnableLog() now supports extended attributes correctly:
243 + Append mode and flush-every-line</li>
244 + <li>Added Session.DoActionSequence() -
245 + This wrapper for the MsiSequence API had been accidentally left out</li>
246 +</ul></li>
247 +<li>CAProxy:<ul>
248 + <li>Catches InstallCanceledException, returns ERROR_INSTALL_USEREXIT
249 + so CA developer doesn't necessarily have to handle the exception</li>
250 +</ul></li>
251 +<li>Msi\Package:<ul>
252 + <li>Added TransformInfo class: metadata about an individual patch transform</li>
253 + <li>Added PatchPackage.GetTransform*() methods which return TransformInfo</li>
254 +</ul></li>
255 +<li>Documentation:<ul>
256 + <li>Added section to ReadMe.htm about building managed custom actions</li>
257 +</ul></li>
258 +</ul>
259 +
260 +<hr size="2"/>
261 +<h3><b>2003-06-02</b></h3>
262 +
263 +<ul>
264 +<li>Msi:<ul>
265 + <li>Validation didn't work on merge modules - fixed</li>
266 +</ul></li>
267 +<li>CAProxy:<ul>
268 + <li>Was broken in 2.1 - fixed</li>
269 +</ul></li>
270 +</ul>
271 +
272 +<hr size="2"/>
273 +<h3><b>2003-05-14</b></h3>
274 +
275 +<ul>
276 +<li>Msi:<ul>
277 + <li>External UI handler didn't survive a garbage collection - fixed</li>
278 + <li>Validation engine was completely broken - now it should work
279 + at least for MSIs which are already mostly valid</li>
280 + <li>Added DynamicLoad property to CustomActionAttribute<br />
281 + Usage: set DynamicLoad=false when using XmlSerialization; default is true</li>
282 +</ul></li>
283 +<li>Msi\Package:<ul>
284 + <li>File extraction and update methods didn't work on merge modules - fixed</li>
285 + <li>Made file update code slightly more robust</li>
286 + <li>Removed hard-reference to the FilePatch assembly - now it is only
287 + loaded if working with binary file patches</li>
288 +</ul></li>
289 +<li>AugmentIL:<ul>
290 + <li>AugmentIL would crash if some input files had read-only attr - fixed</li>
291 + <li>Made /verbose switch slightly more verbose</li>
292 +</ul></li>
293 +<li>CAProxy:<ul>
294 + <li>Added support for the DynamicLoad property of CustomActionAttribute</li>
295 + <li>Added MMsiBreak debugging functionality - see doc</li>
296 +</ul></li>
297 +<li>Samples\WiFile:<ul>
298 + <li>Added /l (list files) switch</li>
299 +</ul></li>
300 +<li>Samples\SampleCAs:<ul>
301 + <li>In the makefile the comments about debug builds had an error;
302 + Now the sample builds debug packages (correctly) by default.</li>
303 +</ul></li>
304 +<li>Documentation:<ul>
305 + <li>Wrote AugmentIL.htm describing the AugmentIL tool and its options.</li>
306 + <li>Wrote WiFile.htm describing the WiFile sample tool.</li>
307 + <li>Added section to ReadMe.htm about debugging managed custom actions.</li>
308 +</ul></li>
309 +</ul>
310 +
311 +<hr size="2"/>
312 +<h3><b>2003-03-31</b></h3>
313 +
314 +<ul>
315 +<li>Msi: Implemented the remaining APIs, also minor improvements and bugfixes<ul>
316 + <li>All published APIs are wrapped, with the exception of four:
317 + MsiGetFileSignatureInformation (because I don't know of a .NET analog
318 + for the returned certificate structure), and 3 APIs for previewing UI</li>
319 + <li>Database.OpenView and Database.Execute* now take String.Format style params</li>
320 + <li>Database.ApplyTransform can optionally use the error-suppression flags
321 + stored in the transform summary info</li>
322 + <li>Added a few supporting enumerations and structures for the remaining APIs</li>
323 + <li>InstallerException gets a descriptive message for any MSI or system error</li>
324 + <li>Fixed a bug in InstallerException which would usually report &quot;error 0&quot;</li>
325 + <li>Added optimization for setting a Record field to a MemoryStream</li>
326 + <li>Record.GetStream is capable of extracting substorages</li>
327 + <li>Moved InstallPath class to Microsoft.WindowsInstaller.Package assembly</li>
328 +</ul></li>
329 +<li>Msi\FilePatch: added new project<ul>
330 + <li>Binary file patch API wrapper</li>
331 +</ul></li>
332 +<li>Msi\Package: added new project<ul>
333 + <li>Helper classes for working with MSI and MSP packages</li>
334 +</ul></li>
335 +<li>Cab: some minor bugfixes<ul>
336 + <li>Cabinet.Extract(stream, name) threw a NullReferenceException if the file
337 + didn't exist in the cab -- now it returns null</li>
338 + <li>CabinetInfo.CompressFileSet() was broken -- fixed</li>
339 + <li>If a Cabinet callback throws an exception, it is propogated as the
340 + inner-exception of the CabinetException</li>
341 +</ul></li>
342 +<li>Samples\WiFile: added new sample<ul>
343 + <li>Demonstrates some features of InstallPackage class in Msi\Package project</li>
344 +</ul></li>
345 +</ul>
346 +
347 +<hr size="2"/>
348 +<h3><b>2003-03-20</b></h3>
349 +
350 +Documentation!<ul>
351 + <li>Msi and Cab sources include complete C# XML documentation.</li>
352 + <li>Msi and Cab makefiles generate XML documentation files.</li>
353 + <li>Reference CHM compiled from XML documentation with NDoc.</li>
354 +</ul>
355 +
356 +<p>I am aware that exceptions are still not documented in most areas.
357 + Other than that, feel free to send me a note if it's still not clear
358 + how to use parts of the API after reading the documentation.</p>
359 +
360 +<p>Version is still 1.1 because there are no code changes in this release.</p>
361 +
362 +<hr size="2"/>
363 +<h3><b>2003-03-13</b></h3>
364 +
365 +<ul>
366 +<li>Msi: lots of small improvements for usability and consistency<ul>
367 + <li>Reworked ExternalUIHandler support</li>
368 + <li>Added Installer properties/methods:<ul>
369 + <li>Components</li>
370 + <li>ComponentClients()</li>
371 + <li>ComponentState()</li>
372 + <li>ComponentPath()</li>
373 + <li>EnableLog()</li>
374 + </ul></li>
375 + <li>Added Session.EvaluateCondition() method</li>
376 + <li>Improved exception-handling in many methods in Installer, Database,
377 + &amp; Session classes</li>
378 + <li>Added extensive XML doc-comments to Installer, Database, View,
379 + &amp; Session classes</li>
380 + <li>A few breaking changes:<ul>
381 + <li>View.ModifyMode enumeration moved outside View and
382 + renamed ViewModifyMode</li>
383 + <li>InstallLogMode enumeration renamed to InstallLogModes
384 + (naming convention for bitfields)</li>
385 + <li>Record constructor takes arbitrary number of parameters</li>
386 + </ul></li>
387 +</ul></li>
388 +<li>AugmentIL: almost completely rewritten<ul>
389 + <li>Ildasm/ilasm steps are built-in<ul>
390 + <li>The round-trip can be done in one step</li>
391 + <li>IL source input/output is still supported</li>
392 + </ul></li>
393 + <li>Never throws an unhandled exception</li>
394 + <li>Organized command-line options, consistent with other .NET tools</li>
395 + <li>Uses a plugin architecture to allow additional augmentations</li>
396 +</ul></li>
397 +<li>CAProxy: Added AIL_CAProxy.cs - AugmentIL plugin generates CA proxy methods</li>
398 +
399 +<li>SampleCAs: Updated makefile for new AugmentIL usage</li>
400 +</ul>
401 +
402 +<hr size="2"/>
403 +<h3><b>2003-01-16</b></h3>
404 +
405 +<ul>
406 +<li>ReadMe.htm: Added section on writing managed CAs</li>
407 +<li>SampleCAs: Added missing reference to System.Windows.Forms.dll to the makefile</li>
408 +<li>AugmentIL: Added specific warning messages for when CA method has wrong signature</li>
409 +<li>Put sources in Toolbox-hosted Source Depot.</li>
410 +</ul>
411 +
412 +<hr size="2"/>
413 +<h3><b>2003-01-14</b></h3>
414 +Initial posting to http://toolbox
415 +
416 +<p>&nbsp;</p>
417 + </div>
418 +
419 + <div id="footer">
420 + <p />
421 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
422 + wix-users@lists.sourceforge.net</a>
423 +
424 + <script type="text/javascript">
425 + var HT_mailLink = document.getElementById("HT_MailLink");
426 + var HT_mailLinkText = HT_mailLink.innerHTML;
427 + HT_mailLink.href += ": " + document.title;
428 + HT_mailLink.innerHTML = HT_mailLinkText;
429 + </script>
430 +
431 + <p />
432 +
433 + </div>
434 + </div>
435 +
436 +</body>
437 +</html>
src/samples/Dtf/Documents/Guide/Content/installutil.htm new
+94
@@ -0,0 +1,94 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>About InstallUtil</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">About InstallUtil</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <span class="nolink">InstallUtil</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p>
26 + InstallUtil is often considered as another option for executing MSI custom actions
27 + written in managed code. But in most cases it is not the best solution, for a number
28 + of reasons.</p>
29 + <p>
30 + InstallUtil (in either InstallUtil.exe or InstallUtilLib.dll form) is a .NET Framework
31 + tool for executing the System.Configuration.Installer classes that are implemented
32 + in an assembly. That way the assembly can contain any special code required to install
33 + itself and uninstall itself. Essentially it is the .NET replacement for COM self-registration
34 + aka DllRegisterServer.</p>
35 + <p>
36 + Self-reg or System.Configuration.Installer is convenient for development use in
37 + order to test code without creating an actual setup package, or for an IDE which
38 + wants to generate self-installing code. But experienced setup developers and the
39 + <a href="MSI.chm::/setup/selfreg_table.htm">Windows Installer documentation</a>
40 + all agree that self-reg is a bad practice for a
41 + production-quality setup. The current theory of state-of-the-art setup is that it
42 + should be as data-driven as possible. That is, the setup package describes as fully
43 + as possible the desired state of the system, and then the installer engine calculates
44 + the necessary actions to install, uninstall, patch, etc.</p>
45 + <p>
46 + S.C.I encourages developers to write code for things such as registering services
47 + or registering COM classes or other things which are more appropriately done using
48 + built-in MSI functionality (the ServiceInstall and Registry tables). The Visual
49 + Studio .NET wizards also tend to generate this kind of install code. Again, that
50 + is nice for development but not good for real installations. You end up with similar
51 + but slightly different code in many places for doing the same thing. And that code
52 + is a black-box to the installer engine.</p>
53 + <p>
54 + An ideal MSI custom action is a logical extension of the setup engine, meaning it
55 + is data-driven and written in a very generic way to read from existing or custom
56 + tables in the MSI database, following a very similar pattern to the built-in actions.
57 + This makes the CA re-usable, and makes the installation more transparent. S.C.I
58 + custom actions invoked by InstallUtil cannot be data-driven because they don't have
59 + full access to the install session or database. They also cannot write to the install
60 + session's regular MSI log, but instead use a separate log which is bad for supportability.</p>
61 + <p>
62 + InstallUtil also requires that the assembly be installed before the CA is able to
63 + execute. This is a problem for CAs that need to execute during the UI phase, or
64 + gather information before installation. For that purpose MSI allows custom action
65 + binaries to be embedded as non-installed files, but InstallUtil cannot make use
66 + of those.</p>
67 + <p>
68 + Custom actions developed with DTF have none of the limitations of InstallUtil,
69 + giving a setup developer full capabilities to write well-designed custom actions,
70 + only now in managed code.
71 + </p>
72 +
73 + <p>&nbsp;</p>
74 + </div>
75 +
76 + <div id="footer">
77 + <p />
78 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
79 + wix-users@lists.sourceforge.net</a>
80 +
81 + <script type="text/javascript">
82 + var HT_mailLink = document.getElementById("HT_MailLink");
83 + var HT_mailLinkText = HT_mailLink.innerHTML;
84 + HT_mailLink.href += ": " + document.title;
85 + HT_mailLink.innerHTML = HT_mailLinkText;
86 + </script>
87 +
88 + <p />
89 +
90 + </div>
91 + </div>
92 +
93 +</body>
94 +</html>
src/samples/Dtf/Documents/Guide/Content/inventory.htm new
+78
@@ -0,0 +1,78 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Windows Installer System Inventory Viewer</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Windows Installer System Inventory Viewer</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="samples.htm">Samples</a> &gt;
17 + <span class="nolink">Inventory</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p>This application shows a hierarchical, relational, searchable
26 + view of all of the product, feature, component, file, and patch
27 + data managed by MSI, for all products installed on the system.</p>
28 + <p><br/></p>
29 +
30 + <h4>Navigation</h4>
31 + <ol>
32 + <li><p>The tree on the left is self-explanatory.</p></li>
33 + <li><p>Click on a row-header (grey box on the left side of the
34 + grid) to jump to a table with more details about the item referred
35 + to by that row. For example, clicking on a row-header of a
36 + table that lists components will take you to a table that lists
37 + the files in that component. Not every table has this ability,
38 + but the cursor will turn to a hand shape to indicate when this is
39 + possible.</p></li>
40 + <li><p>Also you can navigate back and forward through your history
41 + using the buttons in the application or mouse buttons 4 and 5.</p></li>
42 + </ol>
43 + <p><br/></p>
44 +
45 + <h4>Searching</h4>
46 + <p>The search feature is not hard to find. By default, searches
47 + are limited to the current table. However, if you choose to find
48 + &quot;In Subtree&quot; by checking the box, the search will include
49 + the current table as well as all tables under the current location in
50 + the tree. While this can take a long time if there is a lot of
51 + data under the current node, you can stop the search at any time with
52 + the stop button. The search pauses when a match is found, but
53 + clicking &quot;Find&quot; again will continue the same search from that
54 + point (unless you uncheck the &quot;Continue&quot; checkbox or change
55 + the search string).</p>
56 +
57 + <p><br/></p>
58 + </div>
59 +
60 + <div id="footer">
61 + <p />
62 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
63 + wix-users@lists.sourceforge.net</a>
64 +
65 + <script type="text/javascript">
66 + var HT_mailLink = document.getElementById("HT_MailLink");
67 + var HT_mailLinkText = HT_mailLink.innerHTML;
68 + HT_mailLink.href += ": " + document.title;
69 + HT_mailLink.innerHTML = HT_mailLinkText;
70 + </script>
71 +
72 + <p />
73 +
74 + </div>
75 + </div>
76 +
77 +</body>
78 +</html>
src/samples/Dtf/Documents/Guide/Content/managedcas.htm new
+53
@@ -0,0 +1,53 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Managed Custom Actions</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Managed Custom Actions</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <span class="nolink">Managed CAs</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 + <ul>
26 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
27 + <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
28 + <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
29 + <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
30 + <li><a href="installutil.htm">About InstallUtil</a></li>
31 + </ul>
32 +
33 + </div>
34 +
35 + <div id="footer">
36 + <p />
37 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
38 + wix-users@lists.sourceforge.net</a>
39 +
40 + <script type="text/javascript">
41 + var HT_mailLink = document.getElementById("HT_MailLink");
42 + var HT_mailLinkText = HT_mailLink.innerHTML;
43 + HT_mailLink.href += ": " + document.title;
44 + HT_mailLink.innerHTML = HT_mailLinkText;
45 + </script>
46 +
47 + <p />
48 +
49 + </div>
50 + </div>
51 +
52 +</body>
53 +</html>
src/samples/Dtf/Documents/Guide/Content/msihelper.htm new
+59
@@ -0,0 +1,59 @@
1 +<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2 +<html>
3 + <head>
4 + <title>Included Components</title>
5 + <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 + <link rel="stylesheet" type="text/css" href="MSDN.css">
7 + </head>
8 + <body id="bodyID" class="dtBODY">
9 + <div id="nsbanner">
10 + <div id="bannerrow1">
11 + <table class="bannerparthead" cellspacing="0" id="Table1">
12 + <tr id="hdr">
13 + <td class="runninghead">Managed Libraries for Windows Installer</td>
14 + <td class="product"></td>
15 + </tr>
16 + </table>
17 + </div>
18 + <div id="TitleRow">
19 + <h1 class="dtH1">Helper Classes for Windows Installer Packages</h1>
20 + </div>
21 + </div>
22 + <div id="nstext">
23 + <p>Included are some useful helper classes for working with
24 + MSI and MSP packages:</p>
25 + <ul>
26 + <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPackage.html"
27 + ><strong>InstallPackage</strong></a> - extends the Database class to provide powerful
28 + package-based operations such as:</p>
29 + <ul>
30 + <li>direct extraction of files to uncompressed source
31 + path
32 + <li>updating files from uncompressed source path back
33 + into the compressed source for the package (including updating file
34 + metadata)
35 + <li>applying a patch directly to the package
36 + <li>consolidating a package with uncompressed source files or multiple msm-cabs
37 + into a package with a single compressed cabinet</li>
38 + </ul>
39 + <P></P>
40 + <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPathMap.html"
41 + ><strong>InstallPathMap</strong>, <a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.InstallPath.html"
42 + ><strong>InstallPath</strong></a> - represent the directory structure
43 + of an installation package, including file, component, and directory source and target
44 + install paths. Accessible by file, component, or directory keys; searchable by
45 + filename.</p>
46 + <li><p><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.PatchPackage.html"
47 + ><strong>PatchPackage</strong></a> - allows convenient access to patch properties,
48 + and analysis and extraction of transforms</p></li>
49 + </ul>
50 + <p><br/></p>
51 + <p>These classes are in the Microsoft.WindowsInstaller.Package.dll assembly.</p>
52 + <p><br/></p>
53 + <p><b>See also:</b></p>
54 + <p>The <a href="wifile.htm">WiFile</a> sample tool demonstrates some usage of the
55 + InstallPackage class.</p>
56 + <p><br/></p>
57 + </div>
58 + </body>
59 +</html>
src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm new
+80
@@ -0,0 +1,80 @@
1 +<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
2 +<html>
3 + <head>
4 + <title>Included Components</title>
5 + <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
6 + <link rel="stylesheet" type="text/css" href="MSDN.css">
7 + </head>
8 + <body id="bodyID" class="dtBODY">
9 + <div id="nsbanner">
10 + <div id="bannerrow1">
11 + <table class="bannerparthead" cellspacing="0" id="Table1">
12 + <tr id="hdr">
13 + <td class="runninghead">Managed Libraries for Windows Installer</td>
14 + <td class="product"></td>
15 + </tr>
16 + </table>
17 + </div>
18 + <div id="TitleRow">
19 + <h1 class="dtH1">Managed wrapper library for Windows Installer APIs</h1>
20 + </div>
21 + </div>
22 + <div id="nstext">
23 + <p>Microsoft.WindowsInstaller.dll is a complete .NET wrapper for the
24 + Windows Installer APIs. It provides a convenient object model that is
25 + comfortable to .NET developers and still familiar to anyone who has used
26 + the MSI scripting object model.</p>
27 + <h3>Notes</h3>
28 + <ul>
29 + <li><p>All published MSI APIs are wrapped, with the exception of four:
30 + MsiGetFileSignatureInformation (because I don't know of a .NET analog for the
31 + returned certificate structure), and three APIs for previewing UI dialogs.
32 + Other than that, you should be able to do everything that you can
33 + do via the C APIs or the COM automation interface.</p>
34 + <li><p>Some parts of this code have never had a formal test
35 + pass, so use at your own risk. But much of the code is exercised daily, used
36 + by the Developer Division Sustaining Engineering team's BRIQS system to build
37 + and test patches. And it has been in use by many other teams for over two
38 + years now with only a few minor fixes, so it can be considered very stable.</p>
39 + <li><p>Despite its official-sounding namespace, this assembly is not officially
40 + sanctioned by the Windows Installer team. But currently there are not any
41 + plans for an official set of managed even in Longhorn, so I don't see a
42 + conflict for now.</p></li>
43 + </ul>
44 + <h3>Why rewrite it?</h3>
45 + <p>It is possible to access MSI's COM Automation interfaces via C# and VB.NET.
46 + So why create yet another wrapper? Here are some of my reasons:</p>
47 + <ul>
48 + <li><p>One of the primary things I wanted to be able to do
49 + was write custom actions in C#. The automation interface was not usable in
50 + that case, because there is no way to convert the integer session handle
51 + (received as a parameter to the type 1 custom action function) into a Session
52 + automation object.</p>
53 + <li><p>The automation interface does not provide a way to
54 + specify an external UI handler. Besides external UI, this is also needed
55 + to do validation.</p>
56 + <li><p>The automation interface does not provide a way to
57 + explicitly close handles (other than Views). I ran into this problem when I
58 + wanted to programmatically delete a database that I'd just finished using, but
59 + couldn't because it was still open!</p>
60 + <li><p>Finally, COM Automation is somewhat slower than invoking
61 + the APIs directly.</p></li>
62 + </ul>
63 +
64 + <p><br/></p>
65 + <p><b>See also:</b></p>
66 + <ul>
67 + <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.html">Microsoft.WindowsInstaller Namespace</a></li>
68 + <ul>
69 + <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Installer.html">Installer Class</a></li>
70 + <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Database.html">Database Class</a></li>
71 + <li><a href="ms-its:MMLRef.chm::/Microsoft.WindowsInstaller.Session.html">Session Class</a></li>
72 + </ul>
73 + <li><a href="msihelper.htm">Helper Classes for Windows Installer Packages</a></li>
74 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
75 + <li><a href="databases.htm">Working with MSI Databases</a></li>
76 + </ul>
77 + <p><br/></p>
78 + </div>
79 + </body>
80 +</html>
src/samples/Dtf/Documents/Guide/Content/packages.htm new
+86
@@ -0,0 +1,86 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Working with Install Packages</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Working with Install Packages</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <span class="nolink">Install Packages</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 +
25 + <h3>Updating files in a product layout</h3>
26 + <p>The InstallPackage class makes it easy to work with files and cabinets
27 + in the context of a compressed or uncompressed product layout.</p>
28 + <p>This hypothetical example takes an IDictionary 'files' which maps file keys to file paths. Each
29 + file is to be updated in the package layout; cabinets are even recompressed if necessary to include the new files.</p>
30 + <pre><font face="Consolas, Courier New"> <font color=blue>using</font> (InstallPackage pkg = <font color=blue>new</font> InstallPackage(<font color=purple>"d:\builds\product.msi"</font>,
31 + DatabaseOpenMode.Transact))
32 + {
33 + pkg.WorkingDirectory = Path.Combine(Path.GetTempFolder(), <font color=purple>"pkgtmp"</font>);
34 + <font color=blue>foreach</font> (string fileKey in files.Keys)
35 + {
36 + <font color=blue>string</font> sourceFilePath = files[fileKey];
37 + <font color=blue>string</font> destFilePath = pkg.Files[fileKey].SourcePath;
38 + destFilePath = Path.Combine(pkg.WorkingDirectory, destFilePath);
39 + File.Copy(sourceFilePath, destFilePath, <font color=blue>true</font>);
40 + }
41 + pkg.UpdateFiles(<font color=blue>new</font> ArrayList(files.Keys));
42 + pkg.Commit();
43 + Directory.Delete(pkg.WorkingDirectory, <font color=blue>true</font>);
44 + }</font></pre><br />
45 + <p>1.&nbsp; Create a <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage__ctor.htm">new InstallPackage</a>
46 + instance referring to the location of the .msi. This is actually just a specialized subclass of a Database.</p>
47 + <p>2.&nbsp; Set the <a href="DTFAPI.chm::/html/P_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_WorkingDirectory.htm">WorkingDirectory</a>.
48 + This is the root directory where the package expects to find the new source files.</p>
49 + <p>3.&nbsp; Copy each file to its proper location in the working directory. The
50 + <a href="DTFAPI.chm::/html/P_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_Files.htm">InstallPackage.Files</a>
51 + property is used to look up the relative source path of each file.</p>
52 + <p>4.&nbsp; Call <a href="DTFAPI.chm::/html/Overload_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage_UpdateFiles.htm">InstallPackage.UpdateFiles</a>
53 + with the list of file keys. This will re-compress and package the files if necessary, and also update the
54 + following data: File.FileSize, File.Version, File.Language, MsiFileHash.HashPart*.</p>
55 + <p>5.&nbsp; Commit the database changes and cleanup the working directory.</p>
56 + </ul>
57 +
58 + <p><br/></p>
59 + <p><b>See also:</b></p>
60 + <ul>
61 + <li><a href="wifile.htm">WiFile Sample Tool</a> - a more complete tool that expands on the above example.</li>
62 + <li><a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Package_InstallPackage.htm">InstallPackage Class</a></li>
63 + </ul>
64 + <p><br/></p>
65 +
66 + </div>
67 +
68 + <div id="footer">
69 + <p />
70 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
71 + wix-users@lists.sourceforge.net</a>
72 +
73 + <script type="text/javascript">
74 + var HT_mailLink = document.getElementById("HT_MailLink");
75 + var HT_mailLinkText = HT_mailLink.innerHTML;
76 + HT_mailLink.href += ": " + document.title;
77 + HT_mailLink.innerHTML = HT_mailLinkText;
78 + </script>
79 +
80 + <p />
81 +
82 + </div>
83 + </div>
84 +
85 +</body>
86 +</html>
src/samples/Dtf/Documents/Guide/Content/powerdiff.htm new
+71
@@ -0,0 +1,71 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>MSI, MSP, CAB Diff Tool</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">MSI, MSP, CAB Diff Tool</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="samples.htm">Samples</a> &gt;
17 + <span class="nolink">DDiff</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <h2>MSI, MSP, CAB Diff Tool</h2>
26 +
27 + <p><pre><font face="Consolas, Courier New">Usage: DDiff target1 target2 [options]
28 +Example: DDiff d:\dir1 d:\dir2
29 +Example: DDiff setup1.msi setup2.msi
30 +Example: DDiff patch1.msp patch2.msp -patchtarget target.msi
31 +Example: DDiff package1.cab package2.cab
32 +
33 +Options:
34 + /o [filename] Output results to text file (UTF8)
35 + /p [package.msi] Diff patches relative to target MSI</font></pre>
36 + </p>
37 + <p><br/></p>
38 +
39 + <p>The following types of inputs can be diffed:
40 + <ul>
41 + <li><b>Directories</b>: files and subdirectories are compared.</li>
42 + <li><b>Cab files</b>: internal file list and files are compared.</li>
43 + <li><b>MSI/MSM database files</b>: summary info, tables, and embedded binary and cab streams are compared.</li>
44 + <li><b>MSP files</b>: summary info and embedded file cab are compared. When a patch target MSI is provided, the MSP's tables are also compared.</li>
45 + <li><b>Versioned files</b>: Win32 file version is compared.</li>
46 + <li><b>Text files</b>: if diff.exe is in the path, it is used to get a line-by-line diff.</li>
47 + <li><b>Other files</b>: file size and bytes are compared.</li>
48 + </ul>
49 + All processing is done recursively. So a versioned file within a cab within an MSI within a directory will have meaningful diff results.</p>
50 +
51 + <p><br/></p>
52 + </div>
53 + <div id="footer">
54 + <p />
55 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
56 + wix-users@lists.sourceforge.net</a>
57 +
58 + <script type="text/javascript">
59 + var HT_mailLink = document.getElementById("HT_MailLink");
60 + var HT_mailLinkText = HT_mailLink.innerHTML;
61 + HT_mailLink.href += ": " + document.title;
62 + HT_mailLink.innerHTML = HT_mailLinkText;
63 + </script>
64 +
65 + <p />
66 +
67 + </div>
68 + </div>
69 +
70 +</body>
71 +</html>
src/samples/Dtf/Documents/Guide/Content/samplecas.htm new
+84
@@ -0,0 +1,84 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Sample C# Custom Action</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Sample C# Custom Action</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <a href="writingcas.htm">Writing CAs</a> &gt;
18 + <span class="nolink">C# Sample</span>
19 + </span>
20 + </div>
21 + </div>
22 + <div id="main">
23 + <div id="header">
24 + </div>
25 + <div class="summary">
26 +
27 + <p>MSI custom actions are MUCH easier to write in C# than
28 + in C++!</p><pre><font face="Consolas, Courier New"> [CustomAction]
29 + <font color=blue>public</font> <font color=blue>static</font> ActionResult SampleCustomAction1(Session session)
30 + {
31 + session.Log(<font color="purple">"Hello from SampleCA1"</font>);
32 +
33 + <font color=blue>string</font> testProp = session[<font color="purple">"SampleCATest"</font>];
34 + <font color=blue>string</font> testProp2;
35 + testProp2 = (<font color="blue">string</font>) session.Database.ExecuteScalar(
36 + <font color="purple">"SELECT `Value` FROM `Property` WHERE `Property` = 'SampleCATest'"</font>);
37 +
38 + <font color=blue>if</font>(testProp == testProp2)
39 + {
40 + session.Log(<font color="purple">"Simple property test passed."</font>);
41 + <font color=blue>return</font> ActionResult.Success;
42 + }
43 + <font color=blue>else</font>
44 + <font color=blue>return</font> ActionResult.Failure;
45 + }
46 + </font></pre>
47 + <p>A sample CA project with two CAs is included in the
48 + Samples\ManagedCA directory.&nbsp; Running the CustomActionTest project will package the CA and insert
49 + it into a test MSI. The MSI will invoke the custom actions, but it will not install anything
50 + since the second sample CA returns ActionResult.UserExit.
51 + </p>
52 +
53 + <p><br/></p>
54 + <p><b>See also:</b></p>
55 + <ul>
56 + <li><a href="writingcas.htm">Writing Managed Custom Actions</a></li>
57 + <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
58 + <li><a href="databases.htm">Working with MSI Databases</a></li>
59 + <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
60 + <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
61 + </ul>
62 + <p><br/></p>
63 +
64 + </div>
65 +
66 + <div id="footer">
67 + <p />
68 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
69 + wix-users@lists.sourceforge.net</a>
70 +
71 + <script type="text/javascript">
72 + var HT_mailLink = document.getElementById("HT_MailLink");
73 + var HT_mailLinkText = HT_mailLink.innerHTML;
74 + HT_mailLink.href += ": " + document.title;
75 + HT_mailLink.innerHTML = HT_mailLinkText;
76 + </script>
77 +
78 + <p />
79 +
80 + </div>
81 + </div>
82 +
83 +</body>
84 +</html>
src/samples/Dtf/Documents/Guide/Content/samples.htm new
+59
@@ -0,0 +1,59 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Sample Applications</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Sample Applications</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <span class="nolink">Samples</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 + <p>Besides the simple managed custom action sample, there are three functional
25 + and useful sample tools included in this distribution:</p>
26 + <p><a href="Inventory.htm"><b>MSI Inventory</b></a><br/>
27 + Shows a hierarchical, relational, searchable view of all of the product,
28 + feature, component, file, and patch data managed by MSI, for all products
29 + installed on the system.</p>
30 + <p><a href="WiFile.htm"><b>WiFile</b></a><br/>
31 + Extracts and updates cabbed files in an MSI setup.</p>
32 + <p><a href="CabPack.htm"><b>CabPack</b></a><br/>
33 + Creates simple self-extracting cab packages. OK, so this one isn't
34 + especially useful as a tool, but the code should be helpful.</p>
35 + <p><a href="PowerDiff.htm"><b>DDiff</b></a><br/>
36 + Recursively diffs MSI, MSP, CAB, and other files and directories.
37 + Much more thorough than widiffdb.vbs.</p>
38 + <p><br/></p>
39 + </div>
40 +
41 + <div id="footer">
42 + <p />
43 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
44 + wix-users@lists.sourceforge.net</a>
45 +
46 + <script type="text/javascript">
47 + var HT_mailLink = document.getElementById("HT_MailLink");
48 + var HT_mailLinkText = HT_mailLink.innerHTML;
49 + HT_mailLink.href += ": " + document.title;
50 + HT_mailLink.innerHTML = HT_mailLinkText;
51 + </script>
52 +
53 + <p />
54 +
55 + </div>
56 + </div>
57 +
58 +</body>
59 +</html>
src/samples/Dtf/Documents/Guide/Content/support.htm new
+52
@@ -0,0 +1,52 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Support/Bugs</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Support/Bugs</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="about.htm">Overview</a> &gt;
16 + <span class="nolink">Support/Bugs</span>
17 + </span>
18 + </div>
19 + </div>
20 + <div id="main">
21 + <div id="header">
22 + </div>
23 + <div class="summary">
24 + <p>Please send general support questions or comments to the
25 + <a href="mailto:wix-users@sourceforge.net">wix-users</a> discussion list.</p>
26 +
27 + <p>Bugs, suggestions, or feature requests can be submitted at the
28 + <a href="http://wix.sourceforge.net/">WiX project</a>
29 + on Sourceforge.net.</p>
30 +
31 + <p><br/></p>
32 + </div>
33 +
34 + <div id="footer">
35 + <p />
36 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
37 + wix-users@lists.sourceforge.net</a>
38 +
39 + <script type="text/javascript">
40 + var HT_mailLink = document.getElementById("HT_MailLink");
41 + var HT_mailLinkText = HT_mailLink.innerHTML;
42 + HT_mailLink.href += ": " + document.title;
43 + HT_mailLink.innerHTML = HT_mailLinkText;
44 + </script>
45 +
46 + <p />
47 +
48 + </div>
49 + </div>
50 +
51 +</body>
52 +</html>
src/samples/Dtf/Documents/Guide/Content/using.htm new
+50
@@ -0,0 +1,50 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Deployment Tools Foundation Development Guide</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Deployment Tools Foundation Development Guide</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <span class="nolink">Development Guide</span>
16 + </span>
17 + </div>
18 + </div>
19 + <div id="main">
20 + <div id="header">
21 + </div>
22 + <div class="summary">
23 + <ul>
24 + <li><a href="managedcas.htm">Managed Custom Actions</a></li>
25 + <li><a href="databases.htm">Working with MSI Databases</a></li>
26 + <li><a href="cabs.htm">Working with Cabinet Files</a></li>
27 + <li><a href="packages.htm">Working with Install Packages</a></li>
28 + <li><a href="samples.htm">Sample Applications</a></li>
29 + </ul>
30 + </div>
31 +
32 + <div id="footer">
33 + <p />
34 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
35 + wix-users@lists.sourceforge.net</a>
36 +
37 + <script type="text/javascript">
38 + var HT_mailLink = document.getElementById("HT_MailLink");
39 + var HT_mailLinkText = HT_mailLink.innerHTML;
40 + HT_mailLink.href += ": " + document.title;
41 + HT_mailLink.innerHTML = HT_mailLinkText;
42 + </script>
43 +
44 + <p />
45 +
46 + </div>
47 + </div>
48 +
49 +</body>
50 +</html>
src/samples/Dtf/Documents/Guide/Content/whatsnew.htm new
+257
@@ -0,0 +1,257 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>What's New?</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">What's New?</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="about.htm">Overview</a> &gt;
16 + <span class="nolink">What's New?</span>
17 + </span>
18 + <span id="languageFilter">2007-07-03</span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 +
26 + <h3>Highlights</h3>
27 + <ul>
28 + <li><p>New project name name "Deployment Tools Foundation", and
29 + new namespaces <font face="Consolas, Courier New">WixToolset.Dtf.*</font></p></li>
30 + <li><p>Added ZIP compression library</p></li>
31 + <li><p>Added library for reading/writing Win32 resources including file versions</p></li>
32 + <li><p>Managed custom action improvements:</p><ul>
33 + <li><p>Simplified authoring and building -- new MakeSfxCA tool
34 + automatically maps DLL entrypoints to CA methods.</p></li>
35 + <li><p>Managed custom action DLLs now run in a separate process for
36 + better reliability with respect to CLR versions, but still have
37 + full access to the MSI session.</p></li>
38 + </ul></li>
39 + <li><p>Found and fixed many bugs with extensive unit test suite</p></li>
40 + <li><p>LINQ to MSI ! (preview)</p></li>
41 + </ul>
42 +
43 + <p>Unfortunately, all these changes do mean that migrating tools and applications from
44 + the previous release can be a moderate amount of work.</p>
45 +
46 + <h3>Breaking Changes</h3>
47 + <p>For the first time since v1.0, this release contains some major breaking
48 + changes, due to a significant redesign and cleanup effort that has been a
49 + long time coming. The overall purpose of the changes is to bring the class
50 + libraries much closer to ship-quality.</p>
51 + <ul>
52 + <li><p>All libraries use a new namespace hierarchy
53 + under <font face="Consolas, Courier New">WixToolset.Dtf</font>.
54 + The new namespace aligns with the new project name, gives all the various
55 + libraries an identity that makes them obviously related to the DTF project,
56 + and mostly avoids "taking over" a namespace that might be rightfully owned
57 + by the platform technology owner.</p></li>
58 +
59 + <li><p>Assemblies are also renamed to follow namespaces.</p></li>
60 +
61 + <li><p>A new unified compression framework forms the basis for the new ZIP
62 + library and a redesigned CAB library. Additional archive formats can
63 + be plugged into the framework. The stream-based compression APIs have
64 + been redesigned to be more object-oriented and easier to use. The file-based
65 + APIs are mostly unchanged from the old cabinet library, although some names
66 + have changed in order to fit into the new unified framework.</p></li>
67 +
68 + <li><p>Large parts of the WindowsInstaller library have been redesigned
69 + to be more object-oriented and to better follow .NET Framework design
70 + guidelines. And various APIs throughout the library have naming or other
71 + changes for better consistency and to follow conventions and best
72 + pratices as enforced by FxCop.</p></li>
73 +
74 + <li><p>The WindowsInstaller APIs no longer make any attempt to mimic the
75 + MSI COM automation interfaces. The naming and object patterns in the
76 + automation interfaces often conflicted with with best practices for
77 + .NET Framework class libraries. Since most people start using DTF
78 + without having ever experienced MSI scripting, there is little
79 + reason to match the scripting object model. Making the APIs more
80 + consistent with .NET conventions will make them much easier to use
81 + for people already experienced with the .NET Framework.</p></li>
82 +
83 + <li><p>APIs in all class libraries use generics where appropriate, especially
84 + the generic collection interfaces. This means .NET Framework 2.0 or later
85 + is required.</p></li>
86 +
87 + <li><p>The FilePatch library is missing from this release. An updated
88 + and redesigned file-delta library is in development.</p></li>
89 + </ul>
90 +
91 + <h3>Other Changes</h3>
92 + <ul>
93 + <li><p>New MakeSfxCA tool for building managed custom action packages: In addition to
94 + packaging the CA DLL and dependencies, it automatically detects managed CA methods
95 + and generates corresponding unmanaged DLL entrypoints in the CA host DLL (SfxCA.dll),
96 + where they are called by MSI. Previously it was necessary to either provide this
97 + mapping in a CustomAction.config file, or live with the generic "ManagedCustomActionN"
98 + names when authoring the CustomAction table in the MSI. For more info, see the
99 + help topic on building managed custom actions.</p></li>
100 +
101 + <li><p>Out-of-proc managed custom action DLLs:
102 + When a managed custom action runs, it normally requests a specific major
103 + version of the CLR via CustomAction.config. However in the previous implementation,
104 + the request could be ignored if there was already a different version of the CLR
105 + loaded into the MSI process, either from a previous custom action or by some other
106 + means. (The CLR doesn't allow side-by-side versions within the same process.)
107 + While there have been no reports of this issue causing setup failures in practice,
108 + it may be only a matter of time, as new CLR versions keep coming out.</p>
109 +
110 + <p>The redesigned native host for managed custom actions, SfxCA.dll, re-launches
111 + itself in a separate process before loading the CLR and invoking the managed CA.
112 + This ensures that the desired CLR version is always loaded, assuming it is available
113 + on the system. It also sets up a named-pipe remoting channel between the two processes.
114 + All session-related MSI API calls are routed through that channel, so that the
115 + custom action has full access to the installer session just as if it were
116 + running in-process.</p></li>
117 +
118 + <li><p>The new zip compression library supports nearly all features of the zip
119 + file format. This includes the ZIP64 extensions for archives greater than 4GB,
120 + as well as disk-spanning capabilities. Zip encryption is not supported. The zip
121 + library has been tested against a variety of third-party zip tools; please
122 + report any issues with incompatible packages.</p>
123 +
124 + <p>Currently only the basic DEFLATE compression algorithm is supported
125 + (via System.IO.Compression.DeflateStream), and the compression level is not adjustable
126 + when packing an archive. The zip file format has a mechanism for plugging in arbitrary
127 + compression algorithms, and that capability is exposed: you can provide a Stream object
128 + capable of compressing and decompressing bytes as an alternative to DeflateStream.</p></li>
129 +
130 + <li><p>Added support for the few APIs new in MSI 4.0:</p>
131 + <ul>
132 + <li><font face="Consolas, Courier New">Installer.GetPatchFileList()</font></li>
133 + <li><font face="Consolas, Courier New">InstallLogModes.RMFilesInUse</font></li>
134 + <li><font face="Consolas, Courier New">ComponentAttributes.DisableRegistryReflection</font></li>
135 + <li><font face="Consolas, Courier New">ControlAttributes.ElevationShield</font></li>
136 + </ul>&nbsp;<br /></li>
137 +
138 + <li><p>The documentation is now built with the
139 + <a href="http://msdn2.microsoft.com/en-us/vstudio/bb608422.aspx" target="_blank">Sandcastle</a> doc build engine,
140 + with help from the <a href="http://www.codeplex.com/SHFB" target="_blank">Sandcastle
141 + Help File Builder</a>. (The old CHM was built with NDoc.)</p></li>
142 +
143 + <li><p>The documentation includes detailed class diagrams for the
144 + WindowsInstaller and Compression namespaces.</p></li>
145 +
146 + <li><p>WindowsInstaller API doc topics now link straight to the corresponding
147 + unmanaged MSI API topics in MSDN. If you know an unmanaged MSI API you want to
148 + use but don't know the managed equivalent, you can search for it and find what
149 + managed APIs link to it.</p></li>
150 +
151 + <li><p>Unit tests cover about 90% of the Compression, Compression.Zip, and
152 + Compression.Cab assemblies -- basically everything except some rare
153 + error-handling cases.</p></li>
154 +
155 + <li><p>Unit tests along with samples cover over 50% of the WindowsInstaller and
156 + WindowsInstaller.Package assemblies (including custom action functionality). More
157 + test cases are still being added.</p></li>
158 + </ul>
159 +
160 + <h3>Bugfixes</h3>
161 + <p>In addition to the extensive cleanup due to redesigns and unit tests, the following
162 + reported bugs have been fixed:</p>
163 + <ul>
164 + <li><p>Managed custom actions could in rare instances fail to load with error 183
165 + (directory already exists)</p></li>
166 + <li><p>Timestamps of files in a cabinet could be incorrectly offset based on the timezone.
167 + (This was due to a behavior change in the DateTime class between .NET 1.1 and 2.0.)</p></li>
168 + <li><p>Unicode file paths for cabbed files could be handled incorrectly in some cases</p></li>
169 + <li><p>Installer.DetermineApplicablePatches just didn't work</p></li>
170 + <li><p>InstallPackage.ApplyPatch couldn't handle applying multiple patches to the same layout</p></li>
171 + </ul>
172 +
173 + <h3>LINQ to MSI</h3>
174 + <p><i>You'll never want to write MSI SQL again!</i></p>
175 + <p>Language INtegrated Query is a new feature in .NET Framework 3.5 and C# 3.0. Through
176 + a combination of intuitive language syntax and powerful query operations, LINQ provides
177 + a whole new level of productivity for working with data in your code. While the .NET
178 + Framework 3.5 provides LINQ capability for SQL databases and XML data, now you
179 + can write LINQ queries to fetch and even update data in MSI databases!</p>
180 +
181 + <p>Look at the following example:<br />
182 +
183 +<pre><font face="Consolas, Courier New"> <font color="blue">var</font> actions = <font color="blue">from</font> a <font color="blue">in</font> db.InstallExecuteSequences
184 + <font color="blue">join</font> ca <font color="blue">in</font> db.CustomActions <font color="blue">on</font> a.Action <font color="blue">equals</font> ca.Action
185 + <font color="blue">where</font> ca.Type == CustomActionTypes.Dll
186 + <font color="blue">orderby</font> a.Sequence
187 + <font color="blue">select new</font> {
188 + Name = a.Action,
189 + Target = ca.Target,
190 + Sequence = a.Sequence };
191 +
192 + <font color="blue">foreach</font> (<font color="blue">var</font> a <font color="blue">in</font> actions)
193 + {
194 + Console.WriteLine(a);
195 + }
196 + </font></pre>
197 +
198 + The query above gets automatically translated to MSI SQL:</p>
199 +
200 + <p><font face="Consolas, Courier New">&nbsp;&nbsp;&nbsp;&nbsp;SELECT `InstallExecuteSequence`.`Action`,
201 + `CustomAction`.`Target`, `InstallExecuteSequence`.`Sequence` FROM `InstallExecuteSequence`, `CustomAction`
202 + WHERE `InstallExecuteSequence`.Action` = `CustomAction`.`Action` ORDER BY `InstallExecuteSequence`.`Sequence`</font></p>
203 +
204 + <p>But the query is not executed until the <font face="Consolas, Courier New">foreach</font>
205 + enumeration. Then records are fetched from the results incrementally as the enumeration progresses.
206 + The objects fetched are actually of an anonymous type created there in the query with exactly
207 + the desired fields. So the result of this code will be to print the Action, Target, and Sequence
208 + of all Type 1 custom actions.</p>
209 +
210 + <p>The query functionality is currently limited by the capabilities of the MSI SQL engine. For
211 + example, a query can't use <font face="Consolas, Courier New">where (ca.Type &amp;
212 + CustomActionTypes.Dll) != 0</font> because the bitwise-and operator is not supported by
213 + MSI SQL. The preview version of LINQ to MSI will throw an exception for cases like that, but
214 + the eventual goal is to have it automatically move the data and operation outside of MSI when
215 + necessary, so that any arbitrary expressions are supported in the query.</p>
216 +
217 + <p>Note there are no MSI handles (or <font face="Consolas, Courier New">IDisposable</font>s)
218 + to worry about! Handles are all managed internally and closed deterministically. Also,
219 + with the entity object model for common tables, the compiler will tell you if you get a
220 + column name wrong or misspelled. The entity objects even support easy inserting, updating,
221 + and deleting (not shown here).</p>
222 +
223 + <p>For more examples, see the LinqTest project in the source. More documentation
224 + is being written.</p>
225 +
226 + <p>Obviously, LINQ to MSI requires .NET Framework 3.5. Everything else
227 + in DTF requires only .NET Framework 2.0.</p>
228 +
229 + <p><font color="red">Note: The LINQ functionality in this DTF release is of preview quality only
230 + and should not be used in production. While there are unit tests covering a wide
231 + variety of queries, using advanced queries outside what is covered by the tests
232 + is likely to result in unexpected exceptions, and retrieved data might possibly be
233 + incorrect or incomplete. An updated LINQ to MSI library is in development.</font></p>
234 +
235 + <p>&nbsp;</p>
236 +
237 + </div>
238 +
239 + <div id="footer">
240 + <p />
241 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
242 + wix-users@lists.sourceforge.net</a>
243 +
244 + <script type="text/javascript">
245 + var HT_mailLink = document.getElementById("HT_MailLink");
246 + var HT_mailLinkText = HT_mailLink.innerHTML;
247 + HT_mailLink.href += ": " + document.title;
248 + HT_mailLink.innerHTML = HT_mailLinkText;
249 + </script>
250 +
251 + <p />
252 +
253 + </div>
254 + </div>
255 +
256 +</body>
257 +</html>
src/samples/Dtf/Documents/Guide/Content/wifile.htm new
+73
@@ -0,0 +1,73 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Windows Installer Package File Manipulation Tool</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Windows Installer Package File Manipulation Tool</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="samples.htm">Samples</a> &gt;
17 + <span class="nolink">WiFile</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p><pre><font face="Consolas, Courier New">Usage: WiFile.exe package.msi /l [filename,filename2,...]
26 +Usage: WiFile.exe package.msi /x [filename,filename2,...]
27 +Usage: WiFile.exe package.msi /u [filename,filename2,...]
28 +
29 +Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM.
30 +Files are extracted using their source path relative to the package.
31 +Specified filenames do not include paths.
32 +Filenames may be a pattern such as *.exe or file?.dll</font></pre>
33 + </p>
34 + <p><br/></p>
35 +
36 + <h4>Example</h4>
37 + <p>The most powerful use of WiFile.exe is to do a round-trip update of files in a
38 + compressed MSI/MSM package. It works like this:<ol>
39 + <li>Extract specific file(s) or all files from the package:
40 + <tt>WiFile.exe package.msi /x *</tt></li>
41 + <li>The files are now expanded into their directory structure. You can edit/update
42 + the files however you like.</li>
43 + <li>Update the package with the changed files: <tt>WiFile.exe package.msi /u *</tt>&nbsp;
44 + This also updates the file metadata in the MSI including the file version, size, and hash.</li>
45 + </ol></p>
46 + <p><br/></p>
47 +
48 + <h4>Notes</h4>
49 + <ul>
50 + <li><p>Also works with packages that have multiple and/or external cab(s).</p></li>
51 + </ul>
52 +
53 + <p><br/></p>
54 + </div>
55 + <div id="footer">
56 + <p />
57 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
58 + wix-users@lists.sourceforge.net</a>
59 +
60 + <script type="text/javascript">
61 + var HT_mailLink = document.getElementById("HT_MailLink");
62 + var HT_mailLinkText = HT_mailLink.innerHTML;
63 + HT_mailLink.href += ": " + document.title;
64 + HT_mailLink.innerHTML = HT_mailLinkText;
65 + </script>
66 +
67 + <p />
68 +
69 + </div>
70 + </div>
71 +
72 +</body>
73 +</html>
src/samples/Dtf/Documents/Guide/Content/writingcas.htm new
+114
@@ -0,0 +1,114 @@
1 +<html xmlns="http://www.w3.org/1999/xhtml">
2 +<head>
3 + <title>Writing Managed Custom Actions</title>
4 + <link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
5 + <link rel="stylesheet" type="text/css" href="ms-help://Hx/HxRuntime/HxLink.css" />
6 +</head>
7 +
8 +<body>
9 +
10 + <div id="control">
11 + <span class="productTitle">Deployment Tools Foundation</span><br />
12 + <span class="topicTitle">Writing Managed Custom Actions</span><br />
13 + <div id="toolbar">
14 + <span id="chickenFeet">
15 + <a href="using.htm">Development Guide</a> &gt;
16 + <a href="managedcas.htm">Managed CAs</a> &gt;
17 + <span class="nolink">Writing CAs</span>
18 + </span>
19 + </div>
20 + </div>
21 + <div id="main">
22 + <div id="header">
23 + </div>
24 + <div class="summary">
25 + <p><b>Caveats</b></p>
26 + <p>Before choosing to write a custom action in managed code instead of
27 + traditional native C++ code, you should carefully consider the following:</p>
28 + <ul>
29 + <li><p>Obviously, it introduces a dependency on the .NET Framework. Your
30 + MSI package should probably have a LaunchCondition to check for the presence
31 + of the correct version of the .NET Framework before anything else happens.</p></li>
32 + <li><p>If the custom action runs at uninstall time, then even the uninstall of
33 + your product may fail if the .NET Framework is not present. This means a
34 + user could run into a problem if they uninstall the .NET Framework before
35 + your product.</p></li>
36 + <li><p>A managed custom action should be configured to run against a specific
37 + version of the .NET Framework, and that version should match the version your
38 + actual product runs against. Allowing the version to "float" to the latest
39 + installed .NET Framework is likely to lead to compatibility problems with
40 + future versions. The .NET Framework provides side-by-side functionality for
41 + good reason -- use it.</p></li>
42 +
43 + </ul>
44 + <p><br/></p>
45 + <p><b>How To</b></p>
46 + <ul>
47 + <li><p>A custom action function needs to be declared as
48 + <tt>public static</tt> (aka <tt>Public Shared</tt> in VB.NET). It takes one parameter which is
49 + a <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Session.htm">Session</a> object, and returns a
50 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_ActionResult.htm">ActionResult</a> enumeration.</p>
51 + <pre><font face="Consolas, Courier New"> [CustomAction]
52 + <font color=blue>public</font> <font color=blue>static</font> ActionResult MyCustomAction(Session session)</font></pre><br />
53 + <li><p>The function must have a
54 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_CustomActionAttribute.htm"
55 + >CustomActionAttribute</a>, which enables it to be
56 + linked to a proxy function. The attribute can take an optional
57 + &quot;name&quot; parameter, which is the name of the entrypoint
58 + that is exported from the custom action DLL.</p>
59 + <li><p>Fill in MSI CustomAction table entries just like you
60 + would for a normal type 1 native-DLL CA. Managed CAs can also work just
61 + as well in deferred, rollback, and commit modes.</p>
62 + <li><p>If the custom action function throws any kind of
63 + Exception that isn't handled internally, then it will be caught by the proxy
64 + function. The Exception message and stack trace will be printed to the
65 + MSI log if logging is enabled, and the CA will return a failure code.</p>
66 + <li><p>To be technically correct, any MSI handles obtained should be
67 + closed before a custom action function exits -- otherwise a warning
68 + gets printed to the log. The handle classes in the managed library
69 + (<a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Database.htm">Database</a>,
70 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_View.htm">View</a>,
71 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_Record.htm">Record</a>,
72 + <a href="DTFAPI.chm::/html/T_Microsoft_Deployment_WindowsInstaller_SummaryInfo.htm">SummaryInfo</a>)
73 + all implement the IDisposable interface,
74 + which makes them easily managed with C#'s <tt>using</tt>
75 + statement. Alternatively, they can be closed in a finally block.
76 + As a general rule, <i>methods</i> return new handle objects that should be
77 + managed and closed by the user code, while <i>properties</i> only return a reference
78 + to a prexisting handle object.</p></li>
79 + <li><p>Don't forget to use a <a href="caconfig.htm">CustomAction.config</a> file to
80 + specify what version of the .NET Framework the custom action should run against.</p></li>
81 + </ul>
82 +
83 + <p><br/></p>
84 + <p><b>See also:</b></p>
85 + <ul>
86 + <li><a href="samplecas.htm">Sample C# Custom Actions</a></li>
87 + <li><a href="caconfig.htm">Specifying the Runtime Version</a></li>
88 + <li><a href="databases.htm">Working with MSI Databases</a></li>
89 + <li><a href="buildingcas.htm">Building Managed Custom Actions</a></li>
90 + <li><a href="debuggingcas.htm">Debugging Managed Custom Actions</a></li>
91 + </ul>
92 + <p><br/></p>
93 +
94 + </div>
95 +
96 + <div id="footer">
97 + <p />
98 + Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation">
99 + wix-users@lists.sourceforge.net</a>
100 +
101 + <script type="text/javascript">
102 + var HT_mailLink = document.getElementById("HT_MailLink");
103 + var HT_mailLinkText = HT_mailLink.innerHTML;
104 + HT_mailLink.href += ": " + document.title;
105 + HT_mailLink.innerHTML = HT_mailLinkText;
106 + </script>
107 +
108 + <p />
109 +
110 + </div>
111 + </div>
112 +
113 +</body>
114 +</html>
src/samples/Dtf/Documents/Guide/DTF.hhc new
+132
@@ -0,0 +1,132 @@
1 +<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2 +<HTML>
3 +<HEAD>
4 +<!-- Sitemap 1.0 -->
5 +</HEAD><BODY>
6 +<UL>
7 + <LI><OBJECT type="text/sitemap">
8 + <param name="Name" value="Deployment Tools Foundation Overview">
9 + <param name="Local" value="Content\about.htm">
10 + </OBJECT>
11 + <UL>
12 + <LI><OBJECT type="text/sitemap">
13 + <param name="Name" value="What's New?">
14 + <param name="Local" value="Content\whatsnew.htm">
15 + </OBJECT>
16 + </LI>
17 + <LI><OBJECT type="text/sitemap">
18 + <param name="Name" value="Change History">
19 + <param name="Local" value="Content\history.htm">
20 + </OBJECT>
21 + </LI>
22 + <LI><OBJECT type="text/sitemap">
23 + <param name="Name" value="Dependencies">
24 + <param name="Local" value="Content\dependencies.htm">
25 + </OBJECT>
26 + </LI>
27 + <LI><OBJECT type="text/sitemap">
28 + <param name="Name" value="Support/Bugs">
29 + <param name="Local" value="Content\support.htm">
30 + </OBJECT>
31 + </LI>
32 + </UL>
33 + </LI>
34 + <LI><OBJECT type="text/sitemap">
35 + <param name="Name" value="Deployment Tools Foundation Development Guide">
36 + <param name="Local" value="Content\using.htm">
37 + </OBJECT>
38 + <UL>
39 + <LI><OBJECT type="text/sitemap">
40 + <param name="Name" value="Managed Custom Actions">
41 + <param name="Local" value="Content\managedcas.htm">
42 + </OBJECT>
43 + <UL>
44 + <LI><OBJECT type="text/sitemap">
45 + <param name="Name" value="Writing Managed Custom Actions">
46 + <param name="Local" value="Content\writingcas.htm">
47 + </OBJECT>
48 + <UL>
49 + <LI><OBJECT type="text/sitemap">
50 + <param name="Name" value="Specifying the Runtime Version">
51 + <param name="Local" value="Content\caconfig.htm">
52 + </OBJECT>
53 + </LI>
54 + <LI><OBJECT type="text/sitemap">
55 + <param name="Name" value="Sample C# Custom Actions">
56 + <param name="Local" value="Content\samplecas.htm">
57 + </OBJECT>
58 + </LI>
59 + </UL>
60 + </LI>
61 + <LI><OBJECT type="text/sitemap">
62 + <param name="Name" value="Building Managed Custom Actions">
63 + <param name="Local" value="Content\buildingcas.htm">
64 + </OBJECT>
65 + </LI>
66 + <LI><OBJECT type="text/sitemap">
67 + <param name="Name" value="Debugging Managed Custom Actions">
68 + <param name="Local" value="Content\debuggingcas.htm">
69 + </OBJECT>
70 + </LI>
71 + <LI><OBJECT type="text/sitemap">
72 + <param name="Name" value="InstallUtil Notes">
73 + <param name="Local" value="Content\installutil.htm">
74 + </OBJECT>
75 + </LI>
76 + </UL>
77 + </LI>
78 + <LI><OBJECT type="text/sitemap">
79 + <param name="Name" value="Working with MSI Databases">
80 + <param name="Local" value="Content\databases.htm">
81 + </OBJECT>
82 + </LI>
83 + <LI><OBJECT type="text/sitemap">
84 + <param name="Name" value="Working with Cabinet Files">
85 + <param name="Local" value="Content\cabs.htm">
86 + </OBJECT>
87 + </LI>
88 + <LI><OBJECT type="text/sitemap">
89 + <param name="Name" value="Working with Install Packages">
90 + <param name="Local" value="Content\packages.htm">
91 + </OBJECT>
92 + </LI>
93 + <LI><OBJECT type="text/sitemap">
94 + <param name="Name" value="Sample Applications">
95 + <param name="Local" value="Content\samples.htm">
96 + </OBJECT>
97 + <UL>
98 + <LI><OBJECT type="text/sitemap">
99 + <param name="Name" value="MSI Inventory">
100 + <param name="Local" value="Content\inventory.htm">
101 + </OBJECT>
102 + </LI>
103 + <LI><OBJECT type="text/sitemap">
104 + <param name="Name" value="WiFile">
105 + <param name="Local" value="Content\wifile.htm">
106 + </OBJECT>
107 + </LI>
108 + <LI><OBJECT type="text/sitemap">
109 + <param name="Name" value="XPack">
110 + <param name="Local" value="Content\cabpack.htm">
111 + </OBJECT>
112 + </LI>
113 + <LI><OBJECT type="text/sitemap">
114 + <param name="Name" value="DDiff">
115 + <param name="Local" value="Content\powerdiff.htm">
116 + </OBJECT>
117 + </LI>
118 + </UL>
119 + </LI>
120 + </UL>
121 + </LI>
122 + <LI><OBJECT type="text/sitemap">
123 + <param name="Name" value="Deployment Tools Foundation Reference">
124 + <param name="Local" value="DTFAPI.chm::/html/R_Project.htm">
125 + </OBJECT>
126 + <OBJECT type="text/sitemap">
127 + <param name="Merge" value="DTFAPI.chm::/DTFAPI.hhc">
128 + </OBJECT>
129 + </LI>
130 +</UL>
131 +</BODY>
132 +</HTML>
src/samples/Dtf/Documents/Guide/DTF.hhk new
+126
@@ -0,0 +1,126 @@
1 +<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2 +<HTML>
3 +<HEAD>
4 +<!-- Sitemap 1.0 -->
5 +</HEAD><BODY>
6 +<UL>
7 + <LI><OBJECT type="text/sitemap">
8 + <param name="Name" value="Deployment Tools Foundation Overview">
9 + <param name="Local" value="Content\about.htm">
10 + </OBJECT>
11 + </LI>
12 + <LI><OBJECT type="text/sitemap">
13 + <param name="Name" value="What's New?">
14 + <param name="Local" value="Content\whatsnew.htm">
15 + </OBJECT>
16 + </LI>
17 + <LI><OBJECT type="text/sitemap">
18 + <param name="Name" value="Dependencies">
19 + <param name="Local" value="Content\dependencies.htm">
20 + </OBJECT>
21 + </LI>
22 + <LI><OBJECT type="text/sitemap">
23 + <param name="Name" value="Sample Applications">
24 + <param name="Local" value="Content\samples.htm">
25 + </OBJECT>
26 + </LI>
27 + <LI><OBJECT type="text/sitemap">
28 + <param name="Name" value="Inventory Sample Application">
29 + <param name="Local" value="Content\inventory.htm">
30 + </OBJECT>
31 + </LI>
32 + <LI><OBJECT type="text/sitemap">
33 + <param name="Name" value="WiFile Sample Tool">
34 + <param name="Local" value="Content\wifile.htm">
35 + </OBJECT>
36 + </LI>
37 + <LI><OBJECT type="text/sitemap">
38 + <param name="Name" value="CabPack Sample Tool">
39 + <param name="Local" value="Content\cabpack.htm">
40 + </OBJECT>
41 + </LI>
42 + <LI><OBJECT type="text/sitemap">
43 + <param name="Name" value="DDiff Sample Tool">
44 + <param name="Local" value="Content\powerdiff.htm">
45 + </OBJECT>
46 + </LI>
47 + <LI><OBJECT type="text/sitemap">
48 + <param name="Name" value="Support/Bugs">
49 + <param name="Local" value="Content\support.htm">
50 + </OBJECT>
51 + </LI>
52 + <LI><OBJECT type="text/sitemap">
53 + <param name="Name" value="Change History">
54 + <param name="Local" value="Content\history.htm">
55 + </OBJECT>
56 + </LI>
57 + <LI><OBJECT type="text/sitemap">
58 + <param name="Name" value="Using Deployment Tools Foundation">
59 + <param name="Local" value="Content\using.htm">
60 + </OBJECT>
61 + </LI>
62 + <LI><OBJECT type="text/sitemap">
63 + <param name="Name" value="Custom Actions">
64 + <param name="Local" value="Content\managedcas.htm">
65 + </OBJECT>
66 + <UL>
67 + <LI><OBJECT type="text/sitemap">
68 + <param name="Name" value="Writing">
69 + <param name="Local" value="Content\writingcas.htm">
70 + </OBJECT>
71 + </LI>
72 + <LI>
73 + <OBJECT type="text/sitemap">
74 + <param name="Name" value="CustomAction.config">
75 + <param name="Local" value="Content\caconfig.htm">
76 + </OBJECT>
77 + </LI>
78 + <LI><OBJECT type="text/sitemap">
79 + <param name="Name" value="Building">
80 + <param name="Local" value="Content\buildingcas.htm">
81 + </OBJECT>
82 + </LI>
83 + <LI><OBJECT type="text/sitemap">
84 + <param name="Name" value="Debugging">
85 + <param name="Local" value="Content\debuggingcas.htm">
86 + </OBJECT>
87 + </LI>
88 + <LI><OBJECT type="text/sitemap">
89 + <param name="Name" value="Samples">
90 + <param name="Local" value="Content\samplecas.htm">
91 + </OBJECT>
92 + </LI>
93 + </UL>
94 + </LI>
95 + <LI><OBJECT type="text/sitemap">
96 + <param name="Name" value="InstallUtil">
97 + <param name="Local" value="Content\installutil.htm">
98 + </OBJECT>
99 + </LI>
100 + <LI><OBJECT type="text/sitemap">
101 + <param name="Name" value="CustomAction.config file">
102 + <param name="Local" value="Content\caconfig.htm">
103 + </OBJECT>
104 + </LI>
105 + <LI><OBJECT type="text/sitemap">
106 + <param name="Name" value="Sample C# Custom Actions">
107 + <param name="Local" value="Content\samplecas.htm">
108 + </OBJECT>
109 + </LI>
110 + <LI><OBJECT type="text/sitemap">
111 + <param name="Name" value="Databases, Working with">
112 + <param name="Local" value="Content\databases.htm">
113 + </OBJECT>
114 + </LI>
115 + <LI><OBJECT type="text/sitemap">
116 + <param name="Name" value="Cabinets, Working with">
117 + <param name="Local" value="Content\cabs.htm">
118 + </OBJECT>
119 + </LI>
120 + <LI><OBJECT type="text/sitemap">
121 + <param name="Name" value="Packages, Working with">
122 + <param name="Local" value="Content\packages.htm">
123 + </OBJECT>
124 + </LI>
125 +</UL>
126 +</BODY></HTML>
src/samples/Dtf/Documents/Guide/DTF.hhp new
+49
@@ -0,0 +1,49 @@
1 +[OPTIONS]
2 +Auto Index=Yes
3 +Compatibility=1.1 or later
4 +Compiled file=DTF.chm
5 +Contents file=DTF.hhc
6 +Default Window=DTF
7 +Default topic=Content\about.htm
8 +Display compile progress=Yes
9 +Error log file=DTFhelp.log
10 +Full-text search=Yes
11 +Index file=DTF.hhk
12 +Language=0x409 English (United States)
13 +Title=Deployment Tools Foundation
14 +
15 +[WINDOWS]
16 +DTF="Deployment Tools Foundation","DTF.hhc","DTF.hhk","Content\about.htm","Content\about.htm",,,,,0x22520,,0x384e,[143,72,937,601],,,,,,,0
17 +
18 +
19 +[FILES]
20 +styles\presentation.css
21 +Content\about.htm
22 +Content\whatsnew.htm
23 +Content\dependencies.htm
24 +Content\using.htm
25 +DTF.hhc
26 +DTF.hhk
27 +Content\samples.htm
28 +Content\support.htm
29 +Content\history.htm
30 +Content\inventory.htm
31 +Content\wifile.htm
32 +Content\cabpack.htm
33 +Content\powerdiff.htm
34 +Content\cabs.htm
35 +Content\databases.htm
36 +Content\packages.htm
37 +Content\managedcas.htm
38 +Content\samplecas.htm
39 +Content\writingcas.htm
40 +Content\buildingcas.htm
41 +Content\debuggingcas.htm
42 +Content\caproxy.htm
43 +Content\caconfig.htm
44 +Content\installutil.htm
45 +
46 +[MERGE FILES]
47 +DTFAPI.chm
48 +
49 +[INFOTYPES]
src/samples/Dtf/Documents/Guide/dtfguide.helpproj new
+29
@@ -0,0 +1,29 @@
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 +
5 +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 + <PropertyGroup>
7 + <ProjectGuid>{3CFD8620-B41C-470C-ABEF-9D38076A2A8D}</ProjectGuid>
8 + <TargetName>dtf</TargetName>
9 + </PropertyGroup>
10 +
11 + <ItemGroup>
12 + <HelpProjectFile Include="dtf.hhp" />
13 + <HelpProjectContent Include="DTF.hhc" />
14 + <HelpProjectContent Include="DTF.hhk" />
15 + <HelpProjectContent Include="Content\*.*" />
16 + <HelpProjectContent Include="styles\*.*" />
17 + <HelpProjectContent Include="DTFAPI.chm">
18 + <SourcePath>$(OutputPath)DTFAPI.chm</SourcePath>
19 + </HelpProjectContent>
20 + </ItemGroup>
21 +
22 + <ItemGroup>
23 + <ProjectReference Include="..\Reference\dtfref.shfbproj">
24 + <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
25 + </ProjectReference>
26 + </ItemGroup>
27 +
28 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
29 +</Project>
src/samples/Dtf/Documents/Guide/styles/presentation.css new
+394
@@ -0,0 +1,394 @@
1 +
2 +/* page style */
3 +
4 +body {
5 + margin: 0;
6 + background-color: #FFFFFF;
7 + padding: 0;
8 + font-size: 80%;
9 + font-family: verdana, sans-serif;
10 + color: #000000;
11 +}
12 +
13 +table {
14 + /* this is a trick to force tables to inherit the body font size */
15 + font-size: 100%;
16 +}
17 +
18 +/* non-scrolling (control) region style */
19 +
20 +div#control {
21 + margin: 0;
22 + background-color: #D4DFFF;
23 + padding: 4px;
24 + width: 100%;
25 + border-bottom-color: #C8CDDE;
26 + border-bottom-style: solid;
27 + border-bottom-width: 1;
28 +}
29 +
30 +span.productTitle {
31 + font-size: 80%;
32 +}
33 +
34 +span.topicTitle {
35 + font-size: 140%;
36 + font-weight: bold;
37 + color: #003399;
38 +}
39 +
40 +span#chickenFeet {
41 + float: left;
42 +}
43 +
44 +span#languageFilter {
45 + float: right;
46 +}
47 +
48 +/* scrolling (content) region style */
49 +
50 +div#main {
51 + margin: 0;
52 + padding: 1em;
53 + width: 100%;
54 + clear: both;
55 +}
56 +
57 +/* sections */
58 +
59 +div#header {
60 + font-size: 70%;
61 + color: #666666;
62 + margin-bottom: 0.5em;
63 +}
64 +
65 +div.section {
66 + margin-bottom: 1em;
67 +}
68 +
69 +div.sectionTitle {
70 + display: inline;
71 + font-size: 120%;
72 + font-weight: bold;
73 + color: #003399;
74 +}
75 +
76 +div.sectionContent {
77 + margin-top: 0.2em;
78 +}
79 +
80 +span.subsectionTitle {
81 + font-weight: bold;
82 +}
83 +
84 +div#footer {
85 + margin-top: 1em;
86 + border-top: thin solid #003399;
87 + padding-top: 0.5em;
88 +}
89 +
90 +/* authored content (block) */
91 +
92 +p {
93 + margin-top: 0;
94 + margin-bottom: 1em;
95 +}
96 +
97 +dl {
98 + margin-top: 0;
99 + margin-bottom: 1em;
100 +}
101 +
102 +div.code {
103 + clear: both;
104 + width: 100%;
105 + background: #F7F7FF;
106 + padding: 0.4em;
107 + font-family: "Andale Mono";
108 + /* font-family: "Courier New"; */
109 + /* font-family: "This is not a monospace font", monospace; */
110 + font-size: inherit;
111 + margin-bottom: 1em;
112 +}
113 +
114 +pre {
115 + margin: 0;
116 + padding: 0;
117 +}
118 +
119 +table.authoredTable {
120 + table-layout: fixed;
121 + width: 100%;
122 + margin-bottom: 1em;
123 +}
124 +
125 +table.authoredTable th {
126 + border-bottom-color: #C8CDDE;
127 + border-bottom-style: solid;
128 + border-bottom-width: 1;
129 + background: #EFEFF7;
130 + padding: 0.2em;
131 + text-align: left;
132 + color: #000066;
133 + font-weight: bold;
134 +}
135 +
136 +table.authoredTable td {
137 + border-bottom-style: solid;
138 + border-bottom-color: #C8CDDE;
139 + border-bottom-width: 1px;
140 + background: #F7F7FF;
141 + padding: 0.2em;
142 + vertical-align: top;
143 +}
144 +
145 +div.alert {
146 + border: 1px solid #C8CDDE;
147 + background: #F7F7FF;
148 +}
149 +
150 +div.media {
151 + text-align: center;
152 + margin-bottom: 1em;
153 +}
154 +
155 +
156 +/* authored content (inline) */
157 +
158 +span.keyword {
159 + font-weight: bold;
160 +}
161 +
162 +span.code {
163 + font-family: "Andale Mono", "Courier New", Courier, monospace;
164 +}
165 +
166 +/* auto-generated controls */
167 +
168 +div.langTabs {
169 + width: 100%;
170 +}
171 +
172 +div.langTab {
173 + float: left;
174 + width: 16%;
175 + border-top: 1px solid #C8CDDE;
176 + border-left: 1px solid #C8CDDE;
177 + border-right: 1px solid #C8CDDE;
178 + background: #F7F7FF;
179 + padding: 0.2em;
180 + text-align: left;
181 + color: #000066;
182 + font-weight: normal;
183 +}
184 +
185 +div.activeLangTab {
186 + float: left;
187 + width: 16%;
188 + border-top: 1px solid #C8CDDE;
189 + border-left: 1px solid #C8CDDE;
190 + border-right: 1px solid #C8CDDE;
191 + background: #EFEFF7;
192 + padding: 0.2em;
193 + text-align: left;
194 + color: #000066;
195 + font-weight: bold;
196 +}
197 +
198 +table.members {
199 + table-layout: fixed;
200 + width: 100%;
201 +}
202 +
203 +table.members th.iconColumn {
204 + width: 60px;
205 +}
206 +
207 +table.members th.nameColumn {
208 + width: 33%;
209 +}
210 +
211 +table.members th.descriptionColumn {
212 + width: 66%;
213 +}
214 +
215 +table.members th {
216 + border-bottom-color: #C8CDDE;
217 + border-bottom-style: solid;
218 + border-bottom-width: 1;
219 + background: #EFEFF7;
220 + padding: 0.2em;
221 + text-align: left;
222 + color: #000066;
223 + font-weight: bold;
224 +}
225 +
226 +table.members td {
227 + border-bottom-style: solid;
228 + border-bottom-color: #C8CDDE;
229 + border-bottom-width: 1px;
230 + background: #F7F7FF;
231 + padding: 0.2em;
232 + vertical-align: top;
233 + overflow: hidden;
234 +}
235 +
236 +table.exceptions {
237 + table-layout: fixed;
238 + width: 100%;
239 +}
240 +
241 +
242 +table.exceptions th.exceptionNameColumn {
243 + width: 33%;
244 +}
245 +
246 +table.exceptions th.exceptionConditionColumn {
247 + width: 66%;
248 +}
249 +
250 +table.exceptions th {
251 + border-bottom-color: #C8CDDE;
252 + border-bottom-style: solid;
253 + border-bottom-width: 1;
254 + background: #EFEFF7;
255 + padding: 0.2em;
256 + text-align: left;
257 + color: #000066;
258 + font-weight: bold;
259 +}
260 +
261 +table.exceptions td {
262 + border-bottom-style: solid;
263 + border-bottom-color: #C8CDDE;
264 + border-bottom-width: 1px;
265 + background: #F7F7FF;
266 + padding: 0.2em;
267 + vertical-align: top;
268 +}
269 +
270 +table.permissions {
271 + table-layout: fixed;
272 + width: 100%;
273 +}
274 +
275 +
276 +table.permissions th.permissionNameColumn {
277 + width: 33%;
278 +}
279 +
280 +table.permissions th.permissionConditionColumn {
281 + width: 66%;
282 +}
283 +
284 +table.permissions th {
285 + border-bottom-color: #C8CDDE;
286 + border-bottom-style: solid;
287 + border-bottom-width: 1;
288 + background: #EFEFF7;
289 + padding: 0.2em;
290 + text-align: left;
291 + color: #000066;
292 + font-weight: bold;
293 +}
294 +
295 +table.permissions td {
296 + border-bottom-style: solid;
297 + border-bottom-color: #C8CDDE;
298 + border-bottom-width: 1px;
299 + background: #F7F7FF;
300 + padding: 0.2em;
301 + vertical-align: top;
302 +}
303 +
304 +span.obsolete {
305 + color: red;
306 +}
307 +
308 +span.cs {
309 + display: inline;
310 +}
311 +
312 +span.vb {
313 + display: none;
314 +}
315 +
316 +span.cpp {
317 + display: none;
318 +}
319 +/* syntax styling */
320 +
321 +div.code span.identifier {
322 + font-size: 120%;
323 + font-weight: bold;
324 +}
325 +
326 +div.code span.keyword {
327 + color: green;
328 +}
329 +
330 +div.code span.parameter {
331 + font-style: italic;
332 + color: purple;
333 +}
334 +
335 +div.code span.literal {
336 + color: purple;
337 +}
338 +
339 +div.code span.comment {
340 + color: red;
341 +}
342 +
343 +span.foreignPhrase {
344 + font-style: italic;
345 +}
346 +
347 +span.placeholder {
348 + font-style: italic;
349 +}
350 +
351 +a {
352 + color: blue;
353 + font-weight: bold;
354 + text-decoration: none;
355 +}
356 +
357 +MSHelp\:link {
358 + color: blue;
359 + font-weight: bold;
360 + hoverColor: #3366ff;
361 +}
362 +
363 +span.nolink {
364 + font-weight: bold;
365 +}
366 +
367 +table.filter {
368 + table-layout: fixed;
369 +}
370 +
371 +tr.tabs td.tab {
372 + width: 10em;
373 + background: #F7F7FF;
374 + padding: 0.2em;
375 + text-align: left;
376 + color: #000066;
377 + font-weight: normal;
378 + overflow: hidden;
379 + cursor: pointer;
380 +}
381 +
382 +tr.tabs td.activeTab {
383 + width: 10em;
384 + background: #EFEFF7;
385 + padding: 0.2em;
386 + text-align: left;
387 + color: #000066;
388 + font-weight: bold;
389 + overflow: hidden;
390 +}
391 +
392 +td.line {
393 + background: #EFEFF7;
394 +}
src/samples/Dtf/Documents/Reference/Compression.htm new
+13
@@ -0,0 +1,13 @@
1 +<html>
2 +<head>
3 + <title>Class Diagram: WixToolset.Dtf.Compression</title>
4 +</head>
5 +<body>
6 +
7 +<h3><font face="Verdana">WixToolset.Dtf.Compression Namespace</font></h3>
8 +
9 +<img src="Compression1.png" width="870" height="596" border="0" />
10 +<img src="Compression2.png" width="870" height="596" border="0" />
11 +
12 +</body>
13 +</html>
src/samples/Dtf/Documents/Reference/Compression1.png
Binary files /dev/null and b/src/samples/Dtf/Documents/Reference/Compression1.png differ
src/samples/Dtf/Documents/Reference/Compression2.png
Binary files /dev/null and b/src/samples/Dtf/Documents/Reference/Compression2.png differ
src/samples/Dtf/Documents/Reference/WindowsInstaller.htm new
+14
@@ -0,0 +1,14 @@
1 +<html>
2 +<head>
3 + <title>Class Diagram: WixToolset.Dtf.WindowsInstaller</title>
4 +</head>
5 +<body>
6 +
7 +<h3><font face="Verdana">WixToolset.Dtf.WindowsInstaller Namespace</font></h3>
8 +
9 +<img src="WindowsInstaller1.png" width="1136" height="1247" border="0" />
10 +<img src="WindowsInstaller2.png" width="1108" height="1247" border="0" />
11 +<img src="WindowsInstaller3.png" width="866" height="1247" border="0" />
12 +
13 +</body>
14 +</html>
src/samples/Dtf/Documents/Reference/WindowsInstaller1.png
Binary files /dev/null and b/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png differ
src/samples/Dtf/Documents/Reference/WindowsInstaller2.png
Binary files /dev/null and b/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png differ
src/samples/Dtf/Documents/Reference/WindowsInstaller3.png
Binary files /dev/null and b/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png differ
src/samples/Dtf/Documents/Reference/dtfref.shfbproj new
+75
@@ -0,0 +1,75 @@
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 +
5 +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 + <PropertyGroup>
7 + <ProjectGuid>{27C20359-3910-423D-8058-6403935B98C6}</ProjectGuid>
8 +
9 + <Name>Documentation</Name>
10 +
11 + <!-- SHFB properties -->
12 + <SHFBSchemaVersion>1.9.9.0</SHFBSchemaVersion>
13 + <HtmlHelpName>DTFAPI</HtmlHelpName>
14 + <MissingTags>Namespace, TypeParameter</MissingTags>
15 + <VisibleItems>InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected, SealedProtected</VisibleItems>
16 +
17 + <RootNamespaceTitle>Deployment Tools Foundation Namespaces</RootNamespaceTitle>
18 + <HelpTitle>Deployment Tools Foundation</HelpTitle>
19 + <FeedbackEMailAddress>wix-users%40lists.sourceforge.net</FeedbackEMailAddress>
20 + <FooterText>&amp;lt%3bscript src=&amp;quot%3bhelplink.js&amp;quot%3b&amp;gt%3b&amp;lt%3b/script&amp;gt%3b</FooterText>
21 + <PresentationStyle>Prototype</PresentationStyle>
22 + <NamingMethod>MemberName</NamingMethod>
23 + <FrameworkVersion>.NET Framework 3.5</FrameworkVersion>
24 + </PropertyGroup>
25 +
26 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.props" />
27 + <PropertyGroup>
28 + <NamespaceSummaries>
29 + <NamespaceSummaryItem name="(global)" isDocumented="False" xmlns="" />
30 + <NamespaceSummaryItem name="WixToolset.Dtf.Compression" isDocumented="True" xmlns="">Framework for archive packing and unpacking.</NamespaceSummaryItem>
31 + <NamespaceSummaryItem name="WixToolset.Dtf.Compression.Cab" isDocumented="True" xmlns="">Implements cabinet archive packing and unpacking.</NamespaceSummaryItem>
32 + <NamespaceSummaryItem name="WixToolset.Dtf.Compression.Zip" isDocumented="True" xmlns="">Implements zip archive packing and unpacking.</NamespaceSummaryItem>
33 + <NamespaceSummaryItem name="WixToolset.Dtf.Resources" isDocumented="True" xmlns="">Classes for reading and writing resource data in executable files.</NamespaceSummaryItem>
34 + <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller" isDocumented="True" xmlns="">Complete class library for the Windows Installer APIs.</NamespaceSummaryItem>
35 + <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Linq" isDocumented="True" xmlns="">LINQ extensions for querying Windows Installer databases (experimental).</NamespaceSummaryItem>
36 + <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Linq.Entities" isDocumented="False" xmlns="" />
37 + <NamespaceSummaryItem name="WixToolset.Dtf.WindowsInstaller.Package" isDocumented="True" xmlns="">Extended classes for working with Windows Installer installation and patch packages.</NamespaceSummaryItem>
38 + </NamespaceSummaries>
39 +
40 + <DocumentationSources>
41 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.dll" xmlns="" />
42 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.xml" xmlns="" />
43 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Cab.dll" xmlns="" />
44 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Cab.xml" xmlns="" />
45 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Zip.dll" xmlns="" />
46 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Compression.Zip.xml" xmlns="" />
47 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Resources.dll" xmlns="" />
48 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.Resources.xml" xmlns="" />
49 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.dll" xmlns="" />
50 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.xml" xmlns="" />
51 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Package.dll" xmlns="" />
52 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Package.xml" xmlns="" />
53 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Linq.dll" xmlns="" />
54 + <DocumentationSource sourceFile="$(DirTargetWix)\WixToolset.Dtf.WindowsInstaller.Linq.xml" xmlns="" />
55 + </DocumentationSources>
56 + </PropertyGroup>
57 +
58 + <ItemGroup>
59 + <Content Include="helplink.js" />
60 + <Content Include="Compression2.png" />
61 + <Content Include="Compression1.png" />
62 + <Content Include="Compression.htm" />
63 + <Content Include="WindowsInstaller.htm" />
64 + <Content Include="WindowsInstaller3.png" />
65 + <Content Include="WindowsInstaller2.png" />
66 + <Content Include="WindowsInstaller1.png" />
67 + </ItemGroup>
68 +
69 + <ItemGroup>
70 + <Reference Include="System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
71 + <Reference Include="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
72 + </ItemGroup>
73 +
74 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
75 +</Project>
src/samples/Dtf/Documents/Reference/helplink.js new
+184
@@ -0,0 +1,184 @@
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 +FixHelpLinks();
4 +
5 +function GetHelpCode(apiName)
6 +{
7 + switch (apiName.toLowerCase())
8 + {
9 + case "msiadvertiseproduct": return 370056;
10 + case "msiadvertiseproductex": return 370057;
11 + case "msiapplymultiplepatches": return 370059;
12 + case "msiapplypatch": return 370060;
13 + case "msibegintransaction": return 736312;
14 + case "msiclosehandle": return 370067;
15 + case "msicollectuserinfo": return 370068;
16 + case "msiconfigurefeature": return 370069;
17 + case "msiconfigureproduct": return 370070;
18 + case "msiconfigureproductex": return 370071;
19 + case "msicreaterecord": return 370072;
20 + case "msicreatetransformsummaryinfo": return 370073;
21 + case "msidatabaseapplytransform": return 370074;
22 + case "msidatabasecommit": return 370075;
23 + case "msidatabaseexport": return 370076;
24 + case "msidatabasegeneratetransform": return 370077;
25 + case "msidatabasegetprimarykeys": return 370078;
26 + case "msidatabaseimport": return 370079;
27 + case "msidatabaseistablepersistent": return 370080;
28 + case "msidatabasemerge": return 370081;
29 + case "msidatabaseopenview": return 370082;
30 + case "msidetermineapplicablepatches": return 370084;
31 + case "msideterminepatchsequence": return 370085;
32 + case "msidoaction": return 370090;
33 + case "msienablelog": return 370091;
34 + case "msiendtransaction": return 736318;
35 + case "msienumclients": return 370094;
36 + case "msienumcomponentcosts": return 370095;
37 + case "msienumcomponentqualifiers": return 370096;
38 + case "msienumcomponents": return 370097;
39 + case "msienumfeatures": return 370098;
40 + case "msienumpatches": return 370099;
41 + case "msienumpatchesex": return 370100;
42 + case "msienumproducts": return 370101;
43 + case "msienumproductsex": return 370102;
44 + case "msienumrelatedproducts": return 370103;
45 + case "msievaluatecondition": return 370104;
46 + case "msiextractpatchxmldata": return 370105;
47 + case "msiformatrecord": return 370109;
48 + case "msigetactivedatabase": return 370110;
49 + case "msigetcomponentpath": return 370112;
50 + case "msigetcomponentstate": return 370113;
51 + case "msigetdatabasestate": return 370114;
52 + case "msigetfeaturecost": return 370115;
53 + case "msigetfeatureinfo": return 370116;
54 + case "msigetfeaturestate": return 370117;
55 + case "msigetfeatureusage": return 370118;
56 + case "msigetfeaturevalidstates": return 370119;
57 + case "msigetfilehash": return 370120;
58 + case "msigetfileversion": return 370122;
59 + case "msigetlanguage": return 370123;
60 + case "msigetlasterrorrecord": return 370124;
61 + case "msigetmode": return 370125;
62 + case "msigetpatchfilelist": return 370126;
63 + case "msigetpatchinfo": return 370127;
64 + case "msigetpatchinfoex": return 370128;
65 + case "msigetproductcode": return 370129;
66 + case "msigetproductinfo": return 370130;
67 + case "msigetproductinfoex": return 370131;
68 + case "msigetproductinfofromscript": return 370132;
69 + case "msigetproductproperty": return 370133;
70 + case "msigetproperty": return 370134;
71 + case "msigetshortcuttarget": return 370299;
72 + case "msigetsourcepath": return 370300;
73 + case "msigetsummaryinformation": return 370301;
74 + case "msigettargetpath": return 370303;
75 + case "msiinstallmissingcomponent": return 370311;
76 + case "msiinstallmissingfile": return 370313;
77 + case "msiinstallproduct": return 370315;
78 + case "msijointransaction": return 736319;
79 + case "msilocatecomponent": return 370320;
80 + case "msinotifysidchange": return 370328;
81 + case "msiopendatabase": return 370338;
82 + case "msiopenpackage": return 370339;
83 + case "msiopenpackageex": return 370340;
84 + case "msiopenproduct": return 370341;
85 + case "msiprocessadvertisescript": return 370353;
86 + case "msiprocessmessage": return 370354;
87 + case "msiprovideassembly": return 370355;
88 + case "msiprovidecomponent": return 370356;
89 + case "msiprovidequalifiedcomponent": return 370357;
90 + case "msiprovidequalifiedcomponentex":return 370358;
91 + case "msiquerycomponnetstate": return 370360;
92 + case "msiqueryfeaturestate": return 370361;
93 + case "msiqueryfeaturestateex": return 370362;
94 + case "msiqueryproductstate": return 370363;
95 + case "msirecordcleardata": return 370364;
96 + case "msirecorddatasize": return 370365;
97 + case "msirecordgetfieldcount": return 370366;
98 + case "msirecordgetinteger": return 370367;
99 + case "msirecordgetstring": return 370368;
100 + case "msirecordisnull": return 370369;
101 + case "msirecordreadstream": return 370370;
102 + case "msirecordsetinteger": return 370371;
103 + case "msirecordsetstream": return 370372;
104 + case "msirecordsetstring": return 370373;
105 + case "msireinstallfeature": return 370374;
106 + case "msireinstallproduct": return 370375;
107 + case "msiremovepatches": return 370376;
108 + case "msisequence": return 370382;
109 + case "msisetcomponentstate": return 370383;
110 + case "msisetexternalui": return 370384;
111 + case "msisetexternaluirecord": return 370385;
112 + case "msisetfeatureattributes": return 370386;
113 + case "msisetfeaturestate": return 370387;
114 + case "msisetinstalllevel": return 370388;
115 + case "msisetinternalui": return 370389;
116 + case "msisetmode": return 370390;
117 + case "msisetproperty": return 370391;
118 + case "msisettargetpath": return 370392;
119 + case "msisourcelistaddmediadisk": return 370394;
120 + case "msisourcelistaddsource": return 370395;
121 + case "msisourcelistaddsourceex": return 370396;
122 + case "msisourcelistclearall": return 370397;
123 + case "msisourcelistclearallex": return 370398;
124 + case "msisourcelistclearmediadisk": return 370399;
125 + case "msisourcelistclearsource": return 370401;
126 + case "msisourcelistenummediadisks": return 370402;
127 + case "msisourcelistenumsources": return 370403;
128 + case "msisourcelistforceresolution": return 370404;
129 + case "msisourcelistforceresolutionex":return 370405;
130 + case "msisourcelistgetinfo": return 370406;
131 + case "msisourcelistsetinfo": return 370407;
132 + case "msisummaryinfogetproperty": return 370409;
133 + case "msisummaryinfopersist": return 370490;
134 + case "msisummaryinfosetproperty": return 370491;
135 + case "msiusefeature": return 370502;
136 + case "msiusefeatureex": return 370503;
137 + case "msiverifydiskspace": return 370506;
138 + case "msiverifypackage": return 370508;
139 + case "msiviewexecute": return 370513;
140 + case "msiviewfetch": return 370514;
141 + case "msiviewgetcolumninfo": return 370516;
142 + case "msiviewgeterror": return 370518;
143 + case "msiviewmodify": return 370519;
144 + case "productid": return 370855;
145 + default:
146 + return 0;
147 + }
148 +}
149 +
150 +function GetHelpLink(apiName)
151 +{
152 + var helpCode = GetHelpCode(apiName);
153 + if (helpCode != 0)
154 + {
155 + // Found a direct link!
156 + var prefix = (helpCode < 500000 ? "aa" : "bb");
157 + return "http://msdn2.microsoft.com/en-us/library/" + prefix + helpCode + ".aspx";
158 + }
159 + else
160 + {
161 + // This link works, but goes through an annoying 5-sec redirect page.
162 + return "http://msdn.microsoft.com/library/en-us/msi/setup/" + apiName.toLowerCase() + ".asp";
163 + }
164 +}
165 +
166 +// Change any MSI API help links from indirect MSDN references to direct references.
167 +function FixHelpLinks()
168 +{
169 + var msiLinkRegex = /msdn\.microsoft\.com\/library\/en-us\/msi\/setup\/([a-z]+)\.asp/i;
170 + var links = document.body.all.tags("a");
171 + var i;
172 + for (i = 0; i < links.length; i++)
173 + {
174 + var linkElem = links(i);
175 + var match = msiLinkRegex.exec(linkElem.href);
176 + if (match)
177 + {
178 + var apiName = match[1];
179 + linkElem.href = GetHelpLink(apiName);
180 + linkElem.target = "_blank";
181 + linkElem.title = "MSDN Library";
182 + }
183 + }
184 +}
src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Reflection;
4 +
5 +[assembly: AssemblyDescription("Sample managed embedded external UI")]
src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj new
+56
@@ -0,0 +1,56 @@
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 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 + <PropertyGroup>
5 + <ProjectGuid>{864B8C50-7895-4485-AC89-900D86FD8C0D}</ProjectGuid>
6 + <OutputType>Library</OutputType>
7 + <RootNamespace>WixToolset.Dtf.Samples.EmbeddedUI</RootNamespace>
8 + <AssemblyName>WixToolset.Dtf.Samples.EmbeddedUI</AssemblyName>
9 + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
10 + <FileAlignment>512</FileAlignment>
11 + </PropertyGroup>
12 + <ItemGroup>
13 + <Compile Include="AssemblyInfo.cs" />
14 + <Compile Include="InstallProgressCounter.cs" />
15 + <Compile Include="SampleEmbeddedUI.cs" />
16 + <Compile Include="SetupWizard.xaml.cs">
17 + <DependentUpon>SetupWizard.xaml</DependentUpon>
18 + </Compile>
19 + </ItemGroup>
20 + <ItemGroup>
21 + <Page Include="SetupWizard.xaml">
22 + <Generator>MSBuild:Compile</Generator>
23 + <SubType>Designer</SubType>
24 + </Page>
25 + </ItemGroup>
26 + <ItemGroup>
27 + <Reference Include="PresentationCore">
28 + <RequiredTargetFramework>3.0</RequiredTargetFramework>
29 + </Reference>
30 + <Reference Include="PresentationFramework">
31 + <RequiredTargetFramework>3.0</RequiredTargetFramework>
32 + </Reference>
33 + <Reference Include="System" />
34 + <Reference Include="System.Core">
35 + <RequiredTargetFramework>3.5</RequiredTargetFramework>
36 + </Reference>
37 + <Reference Include="System.Xml" />
38 + <Reference Include="WindowsBase">
39 + <RequiredTargetFramework>3.0</RequiredTargetFramework>
40 + </Reference>
41 + </ItemGroup>
42 + <ItemGroup>
43 + <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
44 + <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
45 + <Name>WixToolset.Dtf.WindowsInstaller</Name>
46 + </ProjectReference>
47 + </ItemGroup>
48 +
49 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
50 +<!--
51 + <PropertyGroup>
52 + <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" "$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll"</PostBuildEvent>
53 + </PropertyGroup>
54 +-->
55 +
56 +</Project>
src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs new
+176
@@ -0,0 +1,176 @@
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 +namespace WixToolset.Dtf.Samples.EmbeddedUI
4 +{
5 + using System;
6 + using WixToolset.Dtf.WindowsInstaller;
7 +
8 + /// <summary>
9 + /// Tracks MSI progress messages and converts them to usable progress.
10 + /// </summary>
11 + public class InstallProgressCounter
12 + {
13 + private int total;
14 + private int completed;
15 + private int step;
16 + private bool moveForward;
17 + private bool enableActionData;
18 + private int progressPhase;
19 + private double scriptPhaseWeight;
20 +
21 + public InstallProgressCounter() : this(0.3)
22 + {
23 + }
24 +
25 + public InstallProgressCounter(double scriptPhaseWeight)
26 + {
27 + if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1))
28 + {
29 + throw new ArgumentOutOfRangeException("scriptPhaseWeight");
30 + }
31 +
32 + this.scriptPhaseWeight = scriptPhaseWeight;
33 + }
34 +
35 + /// <summary>
36 + /// Gets a number between 0 and 1 that indicates the overall installation progress.
37 + /// </summary>
38 + public double Progress { get; private set; }
39 +
40 + public void ProcessMessage(InstallMessage messageType, Record messageRecord)
41 + {
42 + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
43 +
44 + switch (messageType)
45 + {
46 + case InstallMessage.ActionStart:
47 + if (this.enableActionData)
48 + {
49 + this.enableActionData = false;
50 + }
51 + break;
52 +
53 + case InstallMessage.ActionData:
54 + if (this.enableActionData)
55 + {
56 + if (this.moveForward)
57 + {
58 + this.completed += this.step;
59 + }
60 + else
61 + {
62 + this.completed -= this.step;
63 + }
64 +
65 + this.UpdateProgress();
66 + }
67 + break;
68 +
69 + case InstallMessage.Progress:
70 + this.ProcessProgressMessage(messageRecord);
71 + break;
72 + }
73 + }
74 +
75 + private void ProcessProgressMessage(Record progressRecord)
76 + {
77 + // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
78 +
79 + if (progressRecord == null || progressRecord.FieldCount == 0)
80 + {
81 + return;
82 + }
83 +
84 + int fieldCount = progressRecord.FieldCount;
85 + int progressType = progressRecord.GetInteger(1);
86 + string progressTypeString = String.Empty;
87 + switch (progressType)
88 + {
89 + case 0: // Master progress reset
90 + if (fieldCount < 4)
91 + {
92 + return;
93 + }
94 +
95 + this.progressPhase++;
96 +
97 + this.total = progressRecord.GetInteger(2);
98 + if (this.progressPhase == 1)
99 + {
100 + // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
101 + // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
102 + // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
103 + // "close" and deal with the rest.
104 + this.total += 50;
105 + }
106 +
107 + this.moveForward = (progressRecord.GetInteger(3) == 0);
108 + this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max
109 + this.enableActionData = false;
110 +
111 + this.UpdateProgress();
112 + break;
113 +
114 + case 1: // Action info
115 + if (fieldCount < 3)
116 + {
117 + return;
118 + }
119 +
120 + if (progressRecord.GetInteger(3) == 0)
121 + {
122 + this.enableActionData = false;
123 + }
124 + else
125 + {
126 + this.enableActionData = true;
127 + this.step = progressRecord.GetInteger(2);
128 + }
129 + break;
130 +
131 + case 2: // Progress report
132 + if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0)
133 + {
134 + return;
135 + }
136 +
137 + if (this.moveForward)
138 + {
139 + this.completed += progressRecord.GetInteger(2);
140 + }
141 + else
142 + {
143 + this.completed -= progressRecord.GetInteger(2);
144 + }
145 +
146 + this.UpdateProgress();
147 + break;
148 +
149 + case 3: // Progress total addition
150 + this.total += progressRecord.GetInteger(2);
151 + break;
152 + }
153 + }
154 +
155 + private void UpdateProgress()
156 + {
157 + if (this.progressPhase < 1 || this.total == 0)
158 + {
159 + this.Progress = 0;
160 + }
161 + else if (this.progressPhase == 1)
162 + {
163 + this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total;
164 + }
165 + else if (this.progressPhase == 2)
166 + {
167 + this.Progress = this.scriptPhaseWeight +
168 + (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total;
169 + }
170 + else
171 + {
172 + this.Progress = 1;
173 + }
174 + }
175 + }
176 +}
src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs new
+132
@@ -0,0 +1,132 @@
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 +namespace WixToolset.Dtf.Samples.EmbeddedUI
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Configuration;
8 + using System.Threading;
9 + using System.Windows;
10 + using System.Windows.Threading;
11 + using WixToolset.Dtf.WindowsInstaller;
12 + using Application = System.Windows.Application;
13 +
14 + public class SampleEmbeddedUI : IEmbeddedUI
15 + {
16 + private Thread appThread;
17 + private Application app;
18 + private SetupWizard setupWizard;
19 + private ManualResetEvent installStartEvent;
20 + private ManualResetEvent installExitEvent;
21 +
22 + /// <summary>
23 + /// Initializes the embedded UI.
24 + /// </summary>
25 + /// <param name="session">Handle to the installer which can be used to get and set properties.
26 + /// The handle is only valid for the duration of this method call.</param>
27 + /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
28 + /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
29 + /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
30 + /// <returns>True if the embedded UI was successfully initialized; false if the installation
31 + /// should continue without the embedded UI.</returns>
32 + /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
33 + /// <exception cref="InstallerException">The embedded UI failed to initialize and
34 + /// causes the installation to fail.</exception>
35 + public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel)
36 + {
37 + if (session != null)
38 + {
39 + if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full)
40 + {
41 + // Don't show custom UI when the UI level is set to basic.
42 + return false;
43 +
44 + // An embedded UI could display an alternate dialog sequence for reduced or
45 + // basic modes, but it's not implemented here. We'll just fall back to the
46 + // built-in MSI basic UI.
47 + }
48 +
49 + if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase))
50 + {
51 + // Don't show custom UI when uninstalling.
52 + return false;
53 +
54 + // An embedded UI could display an uninstall wizard, it's just not imlemented here.
55 + }
56 + }
57 +
58 + // Start the setup wizard on a separate thread.
59 + this.installStartEvent = new ManualResetEvent(false);
60 + this.installExitEvent = new ManualResetEvent(false);
61 + this.appThread = new Thread(this.Run);
62 + this.appThread.SetApartmentState(ApartmentState.STA);
63 + this.appThread.Start();
64 +
65 + // Wait for the setup wizard to either kickoff the install or prematurely exit.
66 + int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent });
67 + if (waitResult == 1)
68 + {
69 + // The setup wizard set the exit event instead of the start event. Cancel the installation.
70 + throw new InstallCanceledException();
71 + }
72 + else
73 + {
74 + // Start the installation with a silenced internal UI.
75 + // This "embedded external UI" will handle message types except for source resolution.
76 + internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly;
77 + return true;
78 + }
79 + }
80 +
81 + /// <summary>
82 + /// Processes information and progress messages sent to the user interface.
83 + /// </summary>
84 + /// <param name="messageType">Message type.</param>
85 + /// <param name="messageRecord">Record that contains message data.</param>
86 + /// <param name="buttons">Message box buttons.</param>
87 + /// <param name="icon">Message box icon.</param>
88 + /// <param name="defaultButton">Message box default button.</param>
89 + /// <returns>Result of processing the message.</returns>
90 + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
91 + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
92 + {
93 + // Synchronously send the message to the setup wizard window on its thread.
94 + object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send,
95 + new Func<MessageResult>(delegate()
96 + {
97 + return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton);
98 + }));
99 + return (MessageResult) result;
100 + }
101 +
102 + /// <summary>
103 + /// Shuts down the embedded UI at the end of the installation.
104 + /// </summary>
105 + /// <remarks>
106 + /// If the installation was canceled during initialization, this method will not be called.
107 + /// If the installation was canceled or failed at any later point, this method will be called at the end.
108 + /// </remarks>
109 + public void Shutdown()
110 + {
111 + // Wait for the user to exit the setup wizard.
112 + this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
113 + new Action(delegate()
114 + {
115 + this.setupWizard.EnableExit();
116 + }));
117 + this.appThread.Join();
118 + }
119 +
120 + /// <summary>
121 + /// Creates the setup wizard and runs the application thread.
122 + /// </summary>
123 + private void Run()
124 + {
125 + this.app = new Application();
126 + this.setupWizard = new SetupWizard(this.installStartEvent);
127 + this.setupWizard.InitializeComponent();
128 + this.app.Run(this.setupWizard);
129 + this.installExitEvent.Set();
130 + }
131 + }
132 +}
src/samples/Dtf/EmbeddedUI/SetupWizard.xaml new
+17
@@ -0,0 +1,17 @@
1 +
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<Window x:Class="WixToolset.Dtf.Samples.EmbeddedUI.SetupWizard"
6 + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7 + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8 + Title="Sample Embedded UI" Height="400" Width="540" Visibility="Visible">
9 + <Grid>
10 + <TextBox Margin="8,8,8,63" Name="messagesTextBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" FontFamily="Lucida Console" FontSize="10" />
11 + <Button Height="23" HorizontalAlignment="Right" Name="installButton" VerticalAlignment="Bottom" Width="75" Click="installButton_Click" Margin="0,0,91,8">Install</Button>
12 + <Button Height="23" HorizontalAlignment="Right" Name="exitButton" VerticalAlignment="Bottom" Width="75" Visibility="Hidden" Click="exitButton_Click" Margin="0,0,8,8">Exit</Button>
13 + <Button Height="23" Margin="0,0,8,8" Name="cancelButton" VerticalAlignment="Bottom" Width="75" HorizontalAlignment="Right" Click="cancelButton_Click">Cancel</Button>
14 + <ProgressBar Height="16" Margin="8,0,8,39" Name="progressBar" VerticalAlignment="Bottom" Visibility="Hidden" IsIndeterminate="False" />
15 + <Label Height="28" HorizontalAlignment="Left" Margin="8,0,0,4.48" Name="progressLabel" VerticalAlignment="Bottom" Width="120" Visibility="Hidden">0%</Label>
16 + </Grid>
17 +</Window>
src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs new
+111
@@ -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 +
3 +namespace WixToolset.Dtf.Samples.EmbeddedUI
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Linq;
8 + using System.Text;
9 + using System.Threading;
10 + using System.Windows;
11 + using System.Windows.Controls;
12 + using System.Windows.Data;
13 + using System.Windows.Documents;
14 + using System.Windows.Input;
15 + using System.Windows.Media;
16 + using System.Windows.Media.Imaging;
17 + using System.Windows.Navigation;
18 + using System.Windows.Shapes;
19 + using WixToolset.Dtf.WindowsInstaller;
20 +
21 + /// <summary>
22 + /// Interaction logic for SetupWizard.xaml
23 + /// </summary>
24 + public partial class SetupWizard : Window
25 + {
26 + private ManualResetEvent installStartEvent;
27 + private InstallProgressCounter progressCounter;
28 + private bool canceled;
29 +
30 + public SetupWizard(ManualResetEvent installStartEvent)
31 + {
32 + this.installStartEvent = installStartEvent;
33 + this.progressCounter = new InstallProgressCounter(0.5);
34 + }
35 +
36 + public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
37 + MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
38 + {
39 + try
40 + {
41 + this.progressCounter.ProcessMessage(messageType, messageRecord);
42 + this.progressBar.Value = this.progressBar.Minimum +
43 + this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum);
44 + this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%";
45 +
46 + switch (messageType)
47 + {
48 + case InstallMessage.Error:
49 + case InstallMessage.Warning:
50 + case InstallMessage.Info:
51 + string message = String.Format("{0}: {1}", messageType, messageRecord);
52 + this.LogMessage(message);
53 + break;
54 + }
55 +
56 + if (this.canceled)
57 + {
58 + this.canceled = false;
59 + return MessageResult.Cancel;
60 + }
61 + }
62 + catch (Exception ex)
63 + {
64 + this.LogMessage(ex.ToString());
65 + this.LogMessage(ex.StackTrace);
66 + }
67 +
68 + return MessageResult.OK;
69 + }
70 +
71 + private void LogMessage(string message)
72 + {
73 + this.messagesTextBox.Text += Environment.NewLine + message;
74 + this.messagesTextBox.ScrollToEnd();
75 + }
76 +
77 + internal void EnableExit()
78 + {
79 + this.progressBar.Visibility = Visibility.Hidden;
80 + this.progressLabel.Visibility = Visibility.Hidden;
81 + this.cancelButton.Visibility = Visibility.Hidden;
82 + this.exitButton.Visibility = Visibility.Visible;
83 + }
84 +
85 + private void installButton_Click(object sender, RoutedEventArgs e)
86 + {
87 + this.installButton.Visibility = Visibility.Hidden;
88 + this.progressBar.Visibility = Visibility.Visible;
89 + this.progressLabel.Visibility = Visibility.Visible;
90 + this.installStartEvent.Set();
91 + }
92 +
93 + private void exitButton_Click(object sender, RoutedEventArgs e)
94 + {
95 + this.Close();
96 + }
97 +
98 + private void cancelButton_Click(object sender, RoutedEventArgs e)
99 + {
100 + if (this.installButton.Visibility == Visibility.Visible)
101 + {
102 + this.Close();
103 + }
104 + else
105 + {
106 + this.canceled = true;
107 + this.cancelButton.IsEnabled = false;
108 + }
109 + }
110 + }
111 +}
src/samples/Dtf/Inventory/Columns.resx new
+252
@@ -0,0 +1,252 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<root>
3 + <!--
4 + Microsoft ResX Schema
5 +
6 + Version 2.0
7 +
8 + The primary goals of this format is to allow a simple XML format
9 + that is mostly human readable. The generation and parsing of the
10 + various data types are done through the TypeConverter classes
11 + associated with the data types.
12 +
13 + Example:
14 +
15 + ... ado.net/XML headers & schema ...
16 + <resheader name="resmimetype">text/microsoft-resx</resheader>
17 + <resheader name="version">2.0</resheader>
18 + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 + <value>[base64 mime encoded serialized .NET Framework object]</value>
24 + </data>
25 + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 + <comment>This is a comment</comment>
28 + </data>
29 +
30 + There are any number of "resheader" rows that contain simple
31 + name/value pairs.
32 +
33 + Each data row contains a name, and value. The row also contains a
34 + type or mimetype. Type corresponds to a .NET class that support
35 + text/value conversion through the TypeConverter architecture.
36 + Classes that don't support this are serialized and stored with the
37 + mimetype set.
38 +
39 + The mimetype is used for serialized objects, and tells the
40 + ResXResourceReader how to depersist the object. This is currently not
41 + extensible. For a given mimetype the value must be set accordingly:
42 +
43 + Note - application/x-microsoft.net.object.binary.base64 is the format
44 + that the ResXResourceWriter will generate, however the reader can
45 + read any of the formats listed below.
46 +
47 + mimetype: application/x-microsoft.net.object.binary.base64
48 + value : The object must be serialized with
49 + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 + : and then encoded with base64 encoding.
51 +
52 + mimetype: application/x-microsoft.net.object.soap.base64
53 + value : The object must be serialized with
54 + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 + : and then encoded with base64 encoding.
56 +
57 + mimetype: application/x-microsoft.net.object.bytearray.base64
58 + value : The object must be serialized into a byte array
59 + : using a System.ComponentModel.TypeConverter
60 + : and then encoded with base64 encoding.
61 + -->
62 + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 + <xsd:element name="root" msdata:IsDataSet="true">
65 + <xsd:complexType>
66 + <xsd:choice maxOccurs="unbounded">
67 + <xsd:element name="metadata">
68 + <xsd:complexType>
69 + <xsd:sequence>
70 + <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 + </xsd:sequence>
72 + <xsd:attribute name="name" use="required" type="xsd:string" />
73 + <xsd:attribute name="type" type="xsd:string" />
74 + <xsd:attribute name="mimetype" type="xsd:string" />
75 + <xsd:attribute ref="xml:space" />
76 + </xsd:complexType>
77 + </xsd:element>
78 + <xsd:element name="assembly">
79 + <xsd:complexType>
80 + <xsd:attribute name="alias" type="xsd:string" />
81 + <xsd:attribute name="name" type="xsd:string" />
82 + </xsd:complexType>
83 + </xsd:element>
84 + <xsd:element name="data">
85 + <xsd:complexType>
86 + <xsd:sequence>
87 + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 + </xsd:sequence>
90 + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 + <xsd:attribute ref="xml:space" />
94 + </xsd:complexType>
95 + </xsd:element>
96 + <xsd:element name="resheader">
97 + <xsd:complexType>
98 + <xsd:sequence>
99 + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 + </xsd:sequence>
101 + <xsd:attribute name="name" type="xsd:string" use="required" />
102 + </xsd:complexType>
103 + </xsd:element>
104 + </xsd:choice>
105 + </xsd:complexType>
106 + </xsd:element>
107 + </xsd:schema>
108 + <resheader name="resmimetype">
109 + <value>text/microsoft-resx</value>
110 + </resheader>
111 + <resheader name="version">
112 + <value>2.0</value>
113 + </resheader>
114 + <resheader name="reader">
115 + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 + </resheader>
117 + <resheader name="writer">
118 + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 + </resheader>
120 + <data name="ProductsProductName" xml:space="preserve">
121 + <value>Product Name,250</value>
122 + </data>
123 + <data name="ProductsProductCode" xml:space="preserve">
124 + <value>Product Code,250</value>
125 + </data>
126 + <data name="ProductPropertiesProperty" xml:space="preserve">
127 + <value>Property,100</value>
128 + </data>
129 + <data name="ProductPropertiesValue" xml:space="preserve">
130 + <value>Value,300</value>
131 + </data>
132 + <data name="ProductFeaturesFeatureTitle" xml:space="preserve">
133 + <value>Feature Title,230</value>
134 + </data>
135 + <data name="ProductFeaturesFeatureName" xml:space="preserve">
136 + <value>Feature,200</value>
137 + </data>
138 + <data name="ProductFeaturesInstallState" xml:space="preserve">
139 + <value>Install State,70</value>
140 + </data>
141 + <data name="ProductFeatureComponentsComponentName" xml:space="preserve">
142 + <value>Component,250</value>
143 + </data>
144 + <data name="ProductFeatureComponentsComponentID" xml:space="preserve">
145 + <value>Component ID,250</value>
146 + </data>
147 + <data name="ProductComponentsComponentName" xml:space="preserve">
148 + <value>Component,180</value>
149 + </data>
150 + <data name="ProductComponentsComponentID" xml:space="preserve">
151 + <value>Component ID,250</value>
152 + </data>
153 + <data name="ProductComponentsInstallState" xml:space="preserve">
154 + <value>Install State,70</value>
155 + </data>
156 + <data name="ComponentProductsProductName" xml:space="preserve">
157 + <value>Product Name,250</value>
158 + </data>
159 + <data name="ComponentProductsProductCode" xml:space="preserve">
160 + <value>Product Code,250</value>
161 + </data>
162 + <data name="ComponentProductsComponentPath" xml:space="preserve">
163 + <value>Component Path,300</value>
164 + </data>
165 + <data name="ProductComponentItemsIsKey" xml:space="preserve">
166 + <value>Key,35</value>
167 + </data>
168 + <data name="ProductComponentItemsKey" xml:space="preserve">
169 + <value>Name,250</value>
170 + </data>
171 + <data name="ProductComponentItemsPath" xml:space="preserve">
172 + <value>Install Path,350</value>
173 + </data>
174 + <data name="ProductComponentItemsExists" xml:space="preserve">
175 + <value>Exists,40</value>
176 + </data>
177 + <data name="ProductComponentItemsDbVersion" xml:space="preserve">
178 + <value>Version in Database,100</value>
179 + </data>
180 + <data name="ProductComponentItemsInstalledVersion" xml:space="preserve">
181 + <value>Version Installed,100</value>
182 + </data>
183 + <data name="ProductComponentItemsInstalledMatch" xml:space="preserve">
184 + <value>Match,40</value>
185 + </data>
186 + <data name="ProductFilesIsKey" xml:space="preserve">
187 + <value>Key,35</value>
188 + </data>
189 + <data name="ProductFilesKey" xml:space="preserve">
190 + <value>Name,250</value>
191 + </data>
192 + <data name="ProductFilesPath" xml:space="preserve">
193 + <value>Install Path,350</value>
194 + </data>
195 + <data name="ProductFilesExists" xml:space="preserve">
196 + <value>Exists,40</value>
197 + </data>
198 + <data name="ProductFilesDbVersion" xml:space="preserve">
199 + <value>Version in Database,120</value>
200 + </data>
201 + <data name="ProductFilesInstalledVersion" xml:space="preserve">
202 + <value>Version Installed,120</value>
203 + </data>
204 + <data name="ProductFilesInstalledMatch" xml:space="preserve">
205 + <value>Match,40</value>
206 + </data>
207 + <data name="ProductFilesComponentID" xml:space="preserve">
208 + <value>Component ID,250</value>
209 + </data>
210 + <data name="ProductRegistryIsKey" xml:space="preserve">
211 + <value>Key,35</value>
212 + </data>
213 + <data name="ProductRegistryKey" xml:space="preserve">
214 + <value>Name,250</value>
215 + </data>
216 + <data name="ProductRegistryPath" xml:space="preserve">
217 + <value>Install Path,350</value>
218 + </data>
219 + <data name="ProductRegistryExists" xml:space="preserve">
220 + <value>Exists,40</value>
221 + </data>
222 + <data name="ProductRegistryDbVersion" xml:space="preserve">
223 + <value>Value in Database,120</value>
224 + </data>
225 + <data name="ProductRegistryInstalledVersion" xml:space="preserve">
226 + <value>Value Installed,120</value>
227 + </data>
228 + <data name="ProductRegistryInstalledMatch" xml:space="preserve">
229 + <value>Match,40</value>
230 + </data>
231 + <data name="ProductRegistryComponentID" xml:space="preserve">
232 + <value>Component ID,250</value>
233 + </data>
234 + <data name="PatchesPatchCode" xml:space="preserve">
235 + <value>Patch Code,250</value>
236 + </data>
237 + <data name="ProductPatchesPatchCode" xml:space="preserve">
238 + <value>Patch Code,250</value>
239 + </data>
240 + <data name="PatchPropertiesProperty" xml:space="preserve">
241 + <value>Property,130</value>
242 + </data>
243 + <data name="PatchPropertiesValue" xml:space="preserve">
244 + <value>Value,360</value>
245 + </data>
246 + <data name="PatchTargetsProductName" xml:space="preserve">
247 + <value>Product Name,360</value>
248 + </data>
249 + <data name="PatchTargetsProductCode" xml:space="preserve">
250 + <value>Product Code,360</value>
251 + </data>
252 +</root>
\ No newline at end of file
src/samples/Dtf/Inventory/Features.cs new
+107
@@ -0,0 +1,107 @@
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 +using System;
4 +using System.IO;
5 +using System.Data;
6 +using System.Collections;
7 +using System.Collections.Generic;
8 +using System.Globalization;
9 +using System.Windows.Forms;
10 +using WixToolset.Dtf.WindowsInstaller;
11 +
12 +namespace WixToolset.Dtf.Samples.Inventory
13 +{
14 + /// <summary>
15 + /// Provides inventory data about features of products installed on the system.
16 + /// </summary>
17 + public class FeaturesInventory : IInventoryDataProvider
18 + {
19 + private static object syncRoot = new object();
20 +
21 + public FeaturesInventory()
22 + {
23 + }
24 +
25 + public string Description
26 + {
27 + get { return "Features of installed products"; }
28 + }
29 +
30 + public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
31 + {
32 + statusCallback(0, @"Products\...\Features");
33 + ArrayList nodes = new ArrayList();
34 + foreach (ProductInstallation product in ProductInstallation.AllProducts)
35 + {
36 + nodes.Add(String.Format(@"Products\{0}\Features", MsiUtils.GetProductName(product.ProductCode)));
37 + }
38 + statusCallback(nodes.Count, String.Empty);
39 + return (string[]) nodes.ToArray(typeof(string));
40 + }
41 +
42 + public bool IsNodeSearchable(string searchRoot, string searchNode)
43 + {
44 + return true;
45 + }
46 +
47 + public DataView GetData(string nodePath)
48 + {
49 + string[] path = nodePath.Split('\\');
50 +
51 + if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
52 + {
53 + return GetProductFeaturesData(MsiUtils.GetProductCode(path[1]));
54 + }
55 + return null;
56 + }
57 +
58 + public DataView GetProductFeaturesData(string productCode)
59 + {
60 + DataTable table = new DataTable("ProductFeatures");
61 + table.Locale = CultureInfo.InvariantCulture;
62 + table.Columns.Add("ProductFeaturesFeatureTitle", typeof(string));
63 + table.Columns.Add("ProductFeaturesFeatureName", typeof(string));
64 + table.Columns.Add("ProductFeaturesInstallState", typeof(string));
65 +
66 + try
67 + {
68 + IntPtr hWnd = IntPtr.Zero;
69 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
70 + lock(syncRoot) // Only one Installer session can be active at a time
71 + {
72 + using(Session session = Installer.OpenProduct(productCode))
73 + {
74 + session.DoAction("CostInitialize");
75 + session.DoAction("FileCost");
76 + session.DoAction("CostFinalize");
77 +
78 + IList<string> featuresAndTitles = session.Database.ExecuteStringQuery(
79 + "SELECT `Title`, `Feature` FROM `Feature`");
80 +
81 + for(int i = 0; i < featuresAndTitles.Count; i += 2)
82 + {
83 + InstallState featureState = session.Features[featuresAndTitles[i + 1]].CurrentState;
84 + table.Rows.Add(new object[] { featuresAndTitles[i], featuresAndTitles[i+1],
85 + (featureState == InstallState.Advertised ? "Advertised" : featureState.ToString()) });
86 + }
87 + }
88 + }
89 + return new DataView(table, "", "ProductFeaturesFeatureTitle ASC", DataViewRowState.CurrentRows);
90 + }
91 + catch(InstallerException) { }
92 + catch(IOException) { }
93 + return null;
94 + }
95 +
96 + public string GetLink(string nodePath, DataRow row)
97 + {
98 + string[] path = nodePath.Split('\\');
99 +
100 + if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
101 + {
102 + return String.Format(@"Products\{0}\Features\{1}", path[1], row["ProductFeaturesFeatureName"]);
103 + }
104 + return null;
105 + }
106 + }
107 +}
src/samples/Dtf/Inventory/IInventoryDataProvider.cs new
+67
@@ -0,0 +1,67 @@
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 +using System;
4 +using System.Data;
5 +
6 +namespace WixToolset.Dtf.Samples.Inventory
7 +{
8 + /// <summary>
9 + /// Reports the total number of items loaded so far by <see cref="IInventoryDataProvider.GetNodes"/>.
10 + /// </summary>
11 + public delegate void InventoryDataLoadStatusCallback(int itemsLoaded, string currentNode);
12 +
13 + /// <summary>
14 + /// Inventory data providers implement this interface to provide a particular type of data.
15 + /// Implementors must provide a parameterless constructor.
16 + /// </summary>
17 + public interface IInventoryDataProvider
18 + {
19 + /// <summary>
20 + /// Gets a description of the data provided. This description allows
21 + /// the user to choose what type of data to gather.
22 + /// </summary>
23 + string Description { get; }
24 +
25 + /// <summary>
26 + /// Gets the paths of all nodes for which this object provides data.
27 + /// </summary>
28 + /// <param name="statusCallback">Callback for reporting status.
29 + /// The callback should not necessarily be invoked for every individual
30 + /// node loaded, rather only every significant chunk.</param>
31 + /// <returns>An array of node paths. The parts of the node paths
32 + /// are delimited by backslashes (\).</returns>
33 + string[] GetNodes(InventoryDataLoadStatusCallback statusCallback);
34 +
35 + /// <summary>
36 + /// When related nodes of a tree consist of duplicate data, it's
37 + /// inefficient to search them all. This method indicates which
38 + /// nodes should be search and which should be ignored.
39 + /// </summary>
40 + /// <param name="searchRoot">Root node of the subtree-search.</param>
41 + /// <param name="searchNode">Node which may or may not be searched.</param>
42 + /// <returns>True if the node should be searched, false otherwise.</returns>
43 + bool IsNodeSearchable(string searchRoot, string searchNode);
44 +
45 + /// <summary>
46 + /// Gets the data for a particular node.
47 + /// </summary>
48 + /// <param name="nodePath">Path of the node for which data is requested.
49 + /// This is one of the paths returned by <see cref="GetNodes"/>.</param>
50 + /// <returns>DataView of a table filled with data, or null if data is
51 + /// not available.</returns>
52 + DataView GetData(string nodePath);
53 +
54 + /// <summary>
55 + /// Gets the path of another node which provides more details about
56 + /// a particular data row.
57 + /// </summary>
58 + /// <param name="nodePath">Path of the node containing the data
59 + /// row being queried.</param>
60 + /// <param name="row">Data row being queried.</param>
61 + /// <returns>Path to another node. This is not necessarily
62 + /// one of the nodes returned by <see cref="GetNodes"/>. If the
63 + /// node path is unknown, it will be ignored. This method may
64 + /// return null if there is no detail node for the row.</returns>
65 + string GetLink(string nodePath, DataRow row);
66 + }
67 +}
src/samples/Dtf/Inventory/Inventory.cs new
+1231
@@ -0,0 +1,1231 @@
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 +using System;
4 +using System.IO;
5 +using System.Drawing;
6 +using System.Collections;
7 +using System.ComponentModel;
8 +using System.Diagnostics.CodeAnalysis;
9 +using System.Windows.Forms;
10 +using System.Globalization;
11 +using System.Reflection;
12 +using System.Resources;
13 +using System.Threading;
14 +using System.Security.Permissions;
15 +using System.Data;
16 +
17 +
18 +[assembly: AssemblyDescription("Shows a hierarchical, relational, searchable " +
19 + " view of all of the product, feature, component, file, and patch data managed " +
20 + "by MSI, for all products installed on the system.")]
21 +
22 +[assembly: SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode=true)]
23 +
24 +
25 +namespace WixToolset.Dtf.Samples.Inventory
26 +{
27 + public class Inventory : System.Windows.Forms.Form
28 + {
29 + [STAThread]
30 + public static void Main()
31 + {
32 + if (WixToolset.Dtf.WindowsInstaller.Installer.Version < new Version(3, 0))
33 + {
34 + MessageBox.Show("This application requires Windows Installer version 3.0 or later.",
35 + "Inventory", MessageBoxButtons.OK, MessageBoxIcon.Error);
36 + return;
37 + }
38 +
39 + Application.Run(new Inventory());
40 + }
41 +
42 + private IInventoryDataProvider[] dataProviders;
43 + private Hashtable dataProviderMap;
44 + private Hashtable data;
45 + private ArrayList tablesLoading;
46 + private bool searching;
47 + private bool stopSearch;
48 + private bool navigating;
49 + private string continueSearchRoot;
50 + private string continueSearchPath;
51 + private DataGridCell continueSearchCell;
52 + private DataGridCell continueSearchEndCell;
53 + private bool mouseOverGridLink = false;
54 + private Stack historyBack;
55 + private Stack historyForward;
56 + private Stack cellHistoryBack;
57 + private Stack cellHistoryForward;
58 + private static readonly DataGridCell anyCell = new DataGridCell(-1,-1);
59 + private static readonly DataGridCell zeroCell = new DataGridCell(0,0);
60 + private static object syncRoot = new object();
61 +
62 + private System.Windows.Forms.DataGrid dataGrid;
63 + private System.Windows.Forms.TreeView treeView;
64 + private System.Windows.Forms.Panel toolPanel;
65 + private System.Windows.Forms.Splitter splitter;
66 + private System.Windows.Forms.Panel dataPanel;
67 + private System.Windows.Forms.Button backButton;
68 + private System.Windows.Forms.Button forwardButton;
69 + private System.Windows.Forms.Button findButton;
70 + private System.Windows.Forms.TextBox findTextBox;
71 + private System.Windows.Forms.Button refreshButton;
72 + private System.Windows.Forms.Button findStopButton;
73 + private System.Windows.Forms.CheckBox searchTreeCheckBox;
74 + private System.Windows.Forms.ToolTip gridLinkTip;
75 + private System.ComponentModel.IContainer components;
76 +
77 + public Inventory()
78 + {
79 + InitializeComponent();
80 +
81 + this.gridLinkTip.InitialDelay = 0;
82 + this.gridLinkTip.ReshowDelay = 0;
83 +
84 + this.dataProviderMap = new Hashtable();
85 + this.data = new Hashtable();
86 + this.tablesLoading = new ArrayList();
87 + this.historyBack = new Stack();
88 + this.historyForward = new Stack();
89 + this.cellHistoryBack = new Stack();
90 + this.cellHistoryForward = new Stack();
91 + }
92 +
93 + protected override void Dispose(bool disposing)
94 + {
95 + if(disposing)
96 + {
97 + if(components != null)
98 + {
99 + components.Dispose();
100 + }
101 + }
102 + base.Dispose(disposing);
103 + }
104 +
105 + #region Windows Form Designer generated code
106 + /// <summary>
107 + /// Required method for Designer support - do not modify
108 + /// the contents of this method with the code editor.
109 + /// </summary>
110 + private void InitializeComponent()
111 + {
112 + this.components = new System.ComponentModel.Container();
113 + this.dataGrid = new System.Windows.Forms.DataGrid();
114 + this.treeView = new System.Windows.Forms.TreeView();
115 + this.toolPanel = new System.Windows.Forms.Panel();
116 + this.findStopButton = new System.Windows.Forms.Button();
117 + this.findButton = new System.Windows.Forms.Button();
118 + this.searchTreeCheckBox = new System.Windows.Forms.CheckBox();
119 + this.findTextBox = new System.Windows.Forms.TextBox();
120 + this.refreshButton = new System.Windows.Forms.Button();
121 + this.forwardButton = new System.Windows.Forms.Button();
122 + this.backButton = new System.Windows.Forms.Button();
123 + this.dataPanel = new System.Windows.Forms.Panel();
124 + this.splitter = new System.Windows.Forms.Splitter();
125 + this.gridLinkTip = new System.Windows.Forms.ToolTip(this.components);
126 + ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).BeginInit();
127 + this.toolPanel.SuspendLayout();
128 + this.dataPanel.SuspendLayout();
129 + this.SuspendLayout();
130 + //
131 + // dataGrid
132 + //
133 + this.dataGrid.DataMember = "";
134 + this.dataGrid.Dock = System.Windows.Forms.DockStyle.Fill;
135 + this.dataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
136 + this.dataGrid.Location = new System.Drawing.Point(230, 0);
137 + this.dataGrid.Name = "dataGrid";
138 + this.dataGrid.ReadOnly = true;
139 + this.dataGrid.SelectionBackColor = System.Drawing.SystemColors.Highlight;
140 + this.dataGrid.Size = new System.Drawing.Size(562, 432);
141 + this.dataGrid.TabIndex = 1;
142 + this.dataGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyDown);
143 + this.dataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseDown);
144 + this.dataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyUp);
145 + this.dataGrid.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseMove);
146 + this.dataGrid.MouseLeave += new System.EventHandler(this.dataGrid_MouseLeave);
147 + //
148 + // treeView
149 + //
150 + this.treeView.Dock = System.Windows.Forms.DockStyle.Left;
151 + this.treeView.HideSelection = false;
152 + this.treeView.ImageIndex = -1;
153 + this.treeView.Location = new System.Drawing.Point(0, 0);
154 + this.treeView.Name = "treeView";
155 + this.treeView.SelectedImageIndex = -1;
156 + this.treeView.Size = new System.Drawing.Size(224, 432);
157 + this.treeView.TabIndex = 0;
158 + this.treeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyDown);
159 + this.treeView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
160 + this.treeView.KeyUp += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyUp);
161 + this.treeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView_AfterSelect);
162 + //
163 + // toolPanel
164 + //
165 + this.toolPanel.Controls.Add(this.findStopButton);
166 + this.toolPanel.Controls.Add(this.findButton);
167 + this.toolPanel.Controls.Add(this.searchTreeCheckBox);
168 + this.toolPanel.Controls.Add(this.findTextBox);
169 + this.toolPanel.Controls.Add(this.refreshButton);
170 + this.toolPanel.Controls.Add(this.forwardButton);
171 + this.toolPanel.Controls.Add(this.backButton);
172 + this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top;
173 + this.toolPanel.Location = new System.Drawing.Point(0, 0);
174 + this.toolPanel.Name = "toolPanel";
175 + this.toolPanel.Size = new System.Drawing.Size(792, 40);
176 + this.toolPanel.TabIndex = 2;
177 + //
178 + // findStopButton
179 + //
180 + this.findStopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
181 + this.findStopButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
182 + this.findStopButton.Location = new System.Drawing.Point(704, 8);
183 + this.findStopButton.Name = "findStopButton";
184 + this.findStopButton.Size = new System.Drawing.Size(72, 25);
185 + this.findStopButton.TabIndex = 6;
186 + this.findStopButton.Text = "Stop";
187 + this.findStopButton.Visible = false;
188 + this.findStopButton.Click += new System.EventHandler(this.findStopButton_Click);
189 + //
190 + // findButton
191 + //
192 + this.findButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
193 + this.findButton.Enabled = false;
194 + this.findButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
195 + this.findButton.Location = new System.Drawing.Point(624, 8);
196 + this.findButton.Name = "findButton";
197 + this.findButton.Size = new System.Drawing.Size(72, 25);
198 + this.findButton.TabIndex = 4;
199 + this.findButton.Text = "Find";
200 + this.findButton.Click += new System.EventHandler(this.findButton_Click);
201 + //
202 + // searchTreeCheckBox
203 + //
204 + this.searchTreeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
205 + this.searchTreeCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System;
206 + this.searchTreeCheckBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
207 + this.searchTreeCheckBox.Location = new System.Drawing.Point(704, 10);
208 + this.searchTreeCheckBox.Name = "searchTreeCheckBox";
209 + this.searchTreeCheckBox.Size = new System.Drawing.Size(80, 22);
210 + this.searchTreeCheckBox.TabIndex = 5;
211 + this.searchTreeCheckBox.Text = "In Subtree";
212 + this.searchTreeCheckBox.CheckedChanged += new System.EventHandler(this.searchTreeCheckBox_CheckedChanged);
213 + //
214 + // findTextBox
215 + //
216 + this.findTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
217 + this.findTextBox.Location = new System.Drawing.Point(344, 10);
218 + this.findTextBox.Name = "findTextBox";
219 + this.findTextBox.Size = new System.Drawing.Size(272, 20);
220 + this.findTextBox.TabIndex = 3;
221 + this.findTextBox.Text = "";
222 + this.findTextBox.TextChanged += new System.EventHandler(this.findTextBox_TextChanged);
223 + this.findTextBox.Enter += new System.EventHandler(this.findTextBox_Enter);
224 + //
225 + // refreshButton
226 + //
227 + this.refreshButton.Enabled = false;
228 + this.refreshButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
229 + this.refreshButton.Location = new System.Drawing.Point(160, 8);
230 + this.refreshButton.Name = "refreshButton";
231 + this.refreshButton.Size = new System.Drawing.Size(72, 25);
232 + this.refreshButton.TabIndex = 2;
233 + this.refreshButton.Text = "Refresh";
234 + this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
235 + //
236 + // forwardButton
237 + //
238 + this.forwardButton.Enabled = false;
239 + this.forwardButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
240 + this.forwardButton.Location = new System.Drawing.Point(80, 8);
241 + this.forwardButton.Name = "forwardButton";
242 + this.forwardButton.Size = new System.Drawing.Size(72, 25);
243 + this.forwardButton.TabIndex = 1;
244 + this.forwardButton.Text = "Forward";
245 + this.forwardButton.Click += new System.EventHandler(this.forwardButton_Click);
246 + //
247 + // backButton
248 + //
249 + this.backButton.Enabled = false;
250 + this.backButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
251 + this.backButton.Location = new System.Drawing.Point(8, 8);
252 + this.backButton.Name = "backButton";
253 + this.backButton.Size = new System.Drawing.Size(72, 25);
254 + this.backButton.TabIndex = 0;
255 + this.backButton.Text = "Back";
256 + this.backButton.Click += new System.EventHandler(this.backButton_Click);
257 + //
258 + // dataPanel
259 + //
260 + this.dataPanel.Controls.Add(this.dataGrid);
261 + this.dataPanel.Controls.Add(this.splitter);
262 + this.dataPanel.Controls.Add(this.treeView);
263 + this.dataPanel.Dock = System.Windows.Forms.DockStyle.Fill;
264 + this.dataPanel.Location = new System.Drawing.Point(0, 40);
265 + this.dataPanel.Name = "dataPanel";
266 + this.dataPanel.Size = new System.Drawing.Size(792, 432);
267 + this.dataPanel.TabIndex = 1;
268 + //
269 + // splitter
270 + //
271 + this.splitter.Location = new System.Drawing.Point(224, 0);
272 + this.splitter.Name = "splitter";
273 + this.splitter.Size = new System.Drawing.Size(6, 432);
274 + this.splitter.TabIndex = 2;
275 + this.splitter.TabStop = false;
276 + //
277 + // Inventory
278 + //
279 + this.AcceptButton = this.findButton;
280 + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
281 + this.ClientSize = new System.Drawing.Size(792, 472);
282 + this.Controls.Add(this.dataPanel);
283 + this.Controls.Add(this.toolPanel);
284 + this.MinimumSize = new System.Drawing.Size(700, 0);
285 + this.Name = "Inventory";
286 + this.Text = "MSI Inventory";
287 + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyDown);
288 + this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
289 + this.Load += new System.EventHandler(this.Inventory_Load);
290 + this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyUp);
291 + ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).EndInit();
292 + this.toolPanel.ResumeLayout(false);
293 + this.dataPanel.ResumeLayout(false);
294 + this.ResumeLayout(false);
295 +
296 + }
297 + #endregion
298 +
299 +
300 + #region DataProviders
301 +
302 + private IInventoryDataProvider[] DataProviders
303 + {
304 + get
305 + {
306 + if(this.dataProviders == null)
307 + {
308 + ArrayList providerList = new ArrayList();
309 + providerList.AddRange(FindDataProviders(Assembly.GetExecutingAssembly()));
310 +
311 + Uri codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
312 + if(codebase.IsFile)
313 + {
314 + foreach(string module in Directory.GetFiles(Path.GetDirectoryName(codebase.LocalPath), "*Inventory.dll"))
315 + {
316 + try
317 + {
318 + providerList.AddRange(FindDataProviders(Assembly.LoadFrom(module)));
319 + }
320 + catch(Exception) { }
321 + }
322 + }
323 +
324 + this.dataProviders = (IInventoryDataProvider[]) providerList.ToArray(typeof(IInventoryDataProvider));
325 + }
326 + return this.dataProviders;
327 + }
328 + }
329 +
330 + private static IList FindDataProviders(Assembly assembly)
331 + {
332 + ArrayList providerList = new ArrayList();
333 + foreach(Type type in assembly.GetTypes())
334 + {
335 + if(type.IsClass)
336 + {
337 + foreach(Type implementedInterface in type.GetInterfaces())
338 + {
339 + if(implementedInterface.Equals(typeof(IInventoryDataProvider)))
340 + {
341 + try
342 + {
343 + providerList.Add(assembly.CreateInstance(type.FullName));
344 + }
345 + catch(Exception)
346 + {
347 + // Data provider's constructor threw an exception for some reason.
348 + // Well, now we can't get any data from that one.
349 + }
350 + }
351 + }
352 + }
353 + }
354 + return providerList;
355 + }
356 +
357 + #endregion
358 +
359 + private void GoTo(string nodePath, DataGridCell cell)
360 + {
361 + lock(syncRoot)
362 + {
363 + if(this.tablesLoading == null) return; // The tree is being loaded
364 + if(this.navigating) return; // This method is already on the callstack
365 +
366 + DataView table = (DataView) this.data[nodePath];
367 + if(table != null && table == this.dataGrid.DataSource)
368 + {
369 + // Grid is already in view
370 + if(!cell.Equals(anyCell)) this.dataGrid.CurrentCell = cell;
371 + return;
372 + }
373 + if(cell.Equals(anyCell)) cell = zeroCell;
374 +
375 + if(this.historyBack.Count == 0 || nodePath != (string) this.historyBack.Peek())
376 + {
377 + this.historyBack.Push(nodePath);
378 + if(this.cellHistoryBack.Count > 0 && this.historyForward != null)
379 + {
380 + this.cellHistoryBack.Pop();
381 + this.cellHistoryBack.Push(this.dataGrid.CurrentCell);
382 + }
383 + this.cellHistoryBack.Push(cell);
384 + }
385 + if(this.historyForward != null)
386 + {
387 + this.historyForward.Clear();
388 + this.cellHistoryForward.Clear();
389 + }
390 +
391 + if(table != null || nodePath.Length == 0 || this.dataProviderMap[nodePath] == null)
392 + {
393 + this.dataGrid.CaptionText = nodePath;
394 + this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
395 + this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
396 + this.dataGrid.DataSource = table;
397 + this.dataGrid.CurrentCell = cell;
398 + this.dataGrid.Focus();
399 + }
400 + else
401 + {
402 + this.dataGrid.CaptionText = nodePath + " (loading...)";
403 + this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
404 + this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
405 + this.dataGrid.DataSource = table;
406 + if(!this.tablesLoading.Contains(nodePath))
407 + {
408 + this.tablesLoading.Add(nodePath);
409 + this.SetCursor();
410 + #if SINGLETHREAD
411 + this.LoadTable(nodePath);
412 + #else
413 + new WaitCallback(this.LoadTable).BeginInvoke(nodePath, null, null);
414 + #endif
415 + }
416 + }
417 +
418 + this.findButton.Enabled = this.findTextBox.Text.Length > 0 && !searching;
419 +
420 + TreeNode treeNode = this.FindNode(nodePath);
421 + if(treeNode != this.treeView.SelectedNode)
422 + {
423 + this.navigating = true;
424 + this.treeView.SelectedNode = treeNode;
425 + this.navigating = false;
426 + }
427 + }
428 + }
429 +
430 + private void LoadTable(object nodePathObj)
431 + {
432 + string nodePath = (string) nodePathObj;
433 + IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
434 + DataView table = null;
435 + if(dataProvider != null)
436 + {
437 + try
438 + {
439 + table = dataProvider.GetData(nodePath);
440 + }
441 + catch(Exception)
442 + {
443 + // Data provider threw an exception for some reason.
444 + // Treat it like it returned no data.
445 + }
446 + }
447 +
448 + lock(syncRoot)
449 + {
450 + if(this.tablesLoading == null || !tablesLoading.Contains(nodePath)) return;
451 + if(table == null)
452 + {
453 + this.dataProviderMap.Remove(nodePath);
454 + }
455 + else
456 + {
457 + this.data[nodePath] = table;
458 + }
459 + this.tablesLoading.Remove(nodePath);
460 + }
461 + #if SINGLETHREAD
462 + this.TableLoaded(nodePath);
463 + #else
464 + this.Invoke(new WaitCallback(this.TableLoaded), new object[] { nodePath });
465 + #endif
466 + }
467 +
468 + private void TableLoaded(object nodePathObj)
469 + {
470 + string nodePath = (string) nodePathObj;
471 + lock(syncRoot)
472 + {
473 + this.LoadTableStyle(nodePath);
474 + if(nodePath == this.CurrentNodePath)
475 + {
476 + this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
477 + this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
478 + this.dataGrid.CaptionText = nodePath;
479 + this.dataGrid.DataSource = this.CurrentTable;
480 + this.dataGrid.CurrentCell = (DataGridCell) this.cellHistoryBack.Peek();
481 + this.dataGrid.Focus();
482 + }
483 + this.SetCursor();
484 + }
485 + }
486 +
487 + private void RefreshData()
488 + {
489 + lock(syncRoot)
490 + {
491 + this.GoTo("", zeroCell);
492 + this.treeView.Nodes.Clear();
493 + this.dataGrid.TableStyles.Clear();
494 + this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
495 + this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
496 + this.SetControlsEnabled(false);
497 + this.treeView.BeginUpdate();
498 + #if SINGLETHREAD
499 + this.LoadTree();
500 + #else
501 + new ThreadStart(this.LoadTree).BeginInvoke(null, null);
502 + #endif
503 + }
504 + }
505 +
506 + private void SetControlsEnabled(bool enabled)
507 + {
508 + this.backButton.Enabled = enabled && this.historyBack.Count > 1;
509 + this.forwardButton.Enabled = enabled && this.historyForward.Count > 0;
510 + this.refreshButton.Enabled = enabled;
511 + this.findButton.Enabled = enabled && this.findTextBox.Text.Length > 0 && !searching;
512 + }
513 +
514 + private WaitCallback treeStatusCallback;
515 + private int treeNodesLoaded;
516 + private int treeNodesLoadedBase;
517 + private string treeNodesLoading;
518 + private void TreeLoadDataProviderStatus(int status, string currentNode)
519 + {
520 + if (currentNode != null)
521 + {
522 + this.treeNodesLoading = currentNode;
523 + }
524 +
525 + this.treeNodesLoaded = treeNodesLoadedBase + status;
526 + string statusString = String.Format("Loading tree... " + this.treeNodesLoaded);
527 + if (!String.IsNullOrEmpty(this.treeNodesLoading))
528 + {
529 + statusString += ": " + treeNodesLoading;
530 + }
531 +
532 + #if SINGLETHREAD
533 + treeStatusCallback(statusString);
534 + #else
535 + this.Invoke(treeStatusCallback, new object[] { statusString });
536 + #endif
537 + }
538 +
539 + private void UpdateTreeLoadStatus(object status)
540 + {
541 + if(status == null)
542 + {
543 + // Loading is complete.
544 + this.treeView.EndUpdate();
545 + this.SetCursor();
546 + this.GoTo("Products", new DataGridCell(0, 0));
547 + this.SetControlsEnabled(true);
548 + }
549 + else
550 + {
551 + this.dataGrid.CaptionText = (string) status;
552 + }
553 + }
554 +
555 + private void LoadTree()
556 + {
557 + lock(syncRoot)
558 + {
559 + if(this.tablesLoading == null) return;
560 + this.tablesLoading = null;
561 + this.dataProviderMap.Clear();
562 + this.data.Clear();
563 + this.Invoke(new ThreadStart(this.SetCursor));
564 + }
565 +
566 + this.treeStatusCallback = new WaitCallback(UpdateTreeLoadStatus);
567 + this.LoadTreeNodes();
568 + this.RenderTreeNodes();
569 +
570 + lock(syncRoot)
571 + {
572 + this.tablesLoading = new ArrayList();
573 + }
574 + // Use a status of null to signal loading complete.
575 + #if SINGLETHREAD
576 + this.UpdateTreeLoadStatus(null);
577 + #else
578 + this.Invoke(new WaitCallback(this.UpdateTreeLoadStatus), new object[] { null });
579 + #endif
580 + }
581 +
582 + private void LoadTreeNodes()
583 + {
584 + #if SINGLETHREAD
585 + this.treeStatusCallback("Loading tree... ");
586 + #else
587 + this.Invoke(this.treeStatusCallback, new object[] { "Loading tree... " });
588 + #endif
589 + this.treeNodesLoaded = 0;
590 + this.treeNodesLoading = null;
591 + foreach(IInventoryDataProvider dataProvider in this.DataProviders)
592 + {
593 + this.treeNodesLoadedBase = this.treeNodesLoaded;
594 + string[] nodePaths = null;
595 + try
596 + {
597 + nodePaths = dataProvider.GetNodes(new InventoryDataLoadStatusCallback(this.TreeLoadDataProviderStatus));
598 + }
599 + catch(Exception)
600 + {
601 + // Data provider threw an exception for some reason.
602 + // Treat it like it returned no data.
603 + }
604 + if(nodePaths != null)
605 + {
606 + foreach(string nodePath in nodePaths)
607 + {
608 + if(!this.dataProviderMap.Contains(nodePath))
609 + {
610 + this.dataProviderMap.Add(nodePath, dataProvider);
611 + }
612 + }
613 + }
614 + }
615 + }
616 +
617 + private void RenderTreeNodes()
618 + {
619 + #if SINGLETHREAD
620 + this.treeStatusCallback("Rendering tree... ");
621 + #else
622 + this.Invoke(this.treeStatusCallback, new object[] { "Rendering tree... " });
623 + #endif
624 + this.treeNodesLoaded = 0;
625 + foreach(DictionaryEntry nodePathAndProvider in this.dataProviderMap)
626 + {
627 + string nodePath = (string) nodePathAndProvider.Key;
628 + #if SINGLETHREAD
629 + this.AddNode(nodePath);
630 + #else
631 + this.Invoke(new WaitCallback(this.AddNode), new object[] { nodePath });
632 + #endif
633 + }
634 + }
635 +
636 + private void LoadTableStyle(string nodePath)
637 + {
638 + DataView table = (DataView) this.data[nodePath];
639 + if(table != null)
640 + {
641 + DataGridTableStyle tableStyle = this.dataGrid.TableStyles[table.Table.TableName];
642 + if(tableStyle == null)
643 + {
644 + tableStyle = new DataGridTableStyle();
645 + tableStyle.MappingName = table.Table.TableName;
646 + tableStyle.RowHeadersVisible = true;
647 + this.dataGrid.TableStyles.Add(tableStyle);
648 + }
649 + foreach(DataColumn column in table.Table.Columns)
650 + {
651 + if(!tableStyle.GridColumnStyles.Contains(column.ColumnName))
652 + {
653 + string colStyle = (string) ColumnResources.GetObject(column.ColumnName, CultureInfo.InvariantCulture);
654 + if(colStyle != null)
655 + {
656 + string[] colStyleParts = colStyle.Split(',');
657 + DataGridColumnStyle columnStyle = (colStyleParts.Length > 2 && colStyleParts[2] == "bool"
658 + ? (DataGridColumnStyle) new DataGridBoolColumn() : (DataGridColumnStyle) new DataGridTextBoxColumn());
659 + try { if(colStyleParts.Length > 1) columnStyle.Width = Int32.Parse(colStyleParts[1]); }
660 + catch(FormatException) { }
661 + columnStyle.HeaderText = colStyleParts[0];
662 + columnStyle.MappingName = column.ColumnName;
663 + tableStyle.GridColumnStyles.Add(columnStyle);
664 + }
665 + }
666 + }
667 + }
668 + }
669 +
670 + private static ResourceManager ColumnResources
671 + {
672 + get
673 + {
674 + if(columnResources == null)
675 + {
676 + columnResources = new ResourceManager(typeof(Inventory).Name + ".Columns", typeof(Inventory).Assembly);
677 + }
678 + return columnResources;
679 + }
680 + }
681 + private static ResourceManager columnResources;
682 +
683 + private void AddNode(object nodePathObj)
684 + {
685 + string nodePath = (string) nodePathObj;
686 + string[] path = nodePath.Split('\\');
687 + TreeNodeCollection nodes = this.treeView.Nodes;
688 + TreeNode node = null;
689 + foreach(string pathPart in path)
690 + {
691 + node = null;
692 + for(int i = 0; i < nodes.Count; i++)
693 + {
694 + int c = string.CompareOrdinal(nodes[i].Text, pathPart);
695 + if(c == 0)
696 + {
697 + node = nodes[i];
698 + break;
699 + }
700 + else if(c > 0)
701 + {
702 + node = new TreeNode(pathPart);
703 + nodes.Insert(i, node);
704 + break;
705 + }
706 + }
707 + if(node == null)
708 + {
709 + node = new TreeNode(pathPart);
710 + nodes.Add(node);
711 + }
712 + nodes = node.Nodes;
713 + }
714 + if(++this.treeNodesLoaded % 1000 == 0)
715 + {
716 + this.UpdateTreeLoadStatus("Rendering tree... " +
717 + (100 * this.treeNodesLoaded / this.dataProviderMap.Count) + "%");
718 + }
719 + }
720 +
721 + public string CurrentNodePath
722 + {
723 + get
724 + {
725 + TreeNode currentNode = this.treeView.SelectedNode;
726 + return currentNode != null ? currentNode.FullPath : null;
727 + }
728 + }
729 +
730 + public DataView CurrentTable
731 + {
732 + get
733 + {
734 + string currentNodePath = this.CurrentNodePath;
735 + return currentNodePath != null ? (DataView) this.data[this.CurrentNodePath] : null;
736 + }
737 + }
738 +
739 + private TreeNode FindNode(string nodePath)
740 + {
741 + if(nodePath == null) return null;
742 + string[] path = nodePath.Split('\\');
743 + TreeNodeCollection nodes = this.treeView.Nodes;
744 + TreeNode node = null;
745 + foreach(string pathPart in path)
746 + {
747 + node = null;
748 + for(int i = 0; i < nodes.Count; i++)
749 + {
750 + if(nodes[i].Text == pathPart)
751 + {
752 + node = nodes[i];
753 + break;
754 + }
755 + }
756 + if(node != null)
757 + {
758 + nodes = node.Nodes;
759 + }
760 + }
761 + return node;
762 + }
763 +
764 + private void dataGrid_MouseDown(object sender, MouseEventArgs e)
765 + {
766 + Keys modKeys = Control.ModifierKeys;
767 + if(e.Button == MouseButtons.Left && (modKeys & (Keys.Shift | Keys.Control)) == 0)
768 + {
769 + DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
770 + string link = this.GetLinkForGridHit(hit);
771 + if(link != null)
772 + {
773 + TreeNode node = this.FindNode(link);
774 + if(node != null)
775 + {
776 + this.treeView.SelectedNode = node;
777 + node.Expand();
778 + }
779 + }
780 + }
781 + this.Inventory_MouseDown(sender, e);
782 + }
783 +
784 + private void dataGrid_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
785 + {
786 + //this.gridLinkTip.SetToolTip(this.dataGrid, null);
787 + DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
788 + if(hit.Type == DataGrid.HitTestType.RowHeader)
789 + {
790 + string link = this.GetLinkForGridHit(hit);
791 + if(link != null)
792 + {
793 + this.mouseOverGridLink = true;
794 + this.SetCursor();
795 + return;
796 + }
797 + }
798 + else if(this.mouseOverGridLink)
799 + {
800 + this.mouseOverGridLink = false;
801 + this.SetCursor();
802 + }
803 + }
804 +
805 + private void dataGrid_MouseLeave(object sender, System.EventArgs e)
806 + {
807 + this.mouseOverGridLink = false;
808 + this.SetCursor();
809 + }
810 +
811 + private string GetLinkForGridHit(DataGrid.HitTestInfo hit)
812 + {
813 + if(hit.Type == DataGrid.HitTestType.RowHeader && this.tablesLoading != null)
814 + {
815 + string nodePath = this.CurrentNodePath;
816 + DataView table = (DataView) this.data[nodePath];
817 + if(table != null)
818 + {
819 + DataRow row = table[hit.Row].Row;
820 + IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
821 + return dataProvider.GetLink(nodePath, table[hit.Row].Row);
822 + }
823 + }
824 + return null;
825 + }
826 +
827 + private void HistoryBack()
828 + {
829 + lock(syncRoot)
830 + {
831 + if(this.historyBack.Count > 1)
832 + {
833 + string nodePath = (string) this.historyBack.Pop();
834 + this.cellHistoryBack.Pop();
835 + DataGridCell cell = this.dataGrid.CurrentCell;
836 + Stack saveForward = this.historyForward;
837 + this.historyForward = null;
838 + this.GoTo((string) this.historyBack.Pop(), (DataGridCell) this.cellHistoryBack.Pop());
839 + this.historyForward = saveForward;
840 + this.historyForward.Push(nodePath);
841 + this.cellHistoryForward.Push(cell);
842 + this.backButton.Enabled = this.historyBack.Count > 1;
843 + this.forwardButton.Enabled = this.historyForward.Count > 0;
844 + }
845 + }
846 + }
847 +
848 + private void HistoryForward()
849 + {
850 + lock(syncRoot)
851 + {
852 + if(this.historyForward.Count > 0)
853 + {
854 + string nodePath = (string) this.historyForward.Pop();
855 + DataGridCell cell = (DataGridCell) this.cellHistoryForward.Pop();
856 + Stack saveForward = this.historyForward;
857 + this.historyForward = null;
858 + this.GoTo(nodePath, cell);
859 + this.historyForward = saveForward;
860 + this.backButton.Enabled = this.historyBack.Count > 1;
861 + this.forwardButton.Enabled = this.historyForward.Count > 0;
862 + }
863 + }
864 + }
865 +
866 + #region Find
867 +
868 + private void Find()
869 + {
870 + this.BeginFind();
871 + object[] findNextArgs = new object[] { this.CurrentNodePath, this.dataGrid.CurrentCell, this.treeView.SelectedNode };
872 + #if SINGLETHREAD
873 + this.FindNext(findNextArgs);
874 + #else
875 + new WaitCallback(this.FindNext).BeginInvoke(findNextArgs, null, null);
876 + #endif
877 + }
878 +
879 + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
880 + private void FindNext(object start)
881 + {
882 + string nodePath = (string) ((object[]) start)[0];
883 + DataGridCell startCell = (DataGridCell) ((object[]) start)[1];
884 + TreeNode searchNode = (TreeNode) ((object[]) start)[2];
885 + DataGridCell endCell = startCell;
886 +
887 + string searchString = this.findTextBox.Text;
888 + if(searchString.Length == 0) return;
889 +
890 + bool ignoreCase = true; // TODO: make this a configurable option?
891 + if(ignoreCase) searchString = searchString.ToLowerInvariant();
892 +
893 + if(!this.searchTreeCheckBox.Checked)
894 + {
895 + DataGridCell foundCell;
896 + startCell.ColumnNumber++;
897 + if(FindInTable((DataView) this.data[nodePath], searchString, ignoreCase,
898 + startCell, startCell, true, out foundCell))
899 + {
900 + #if SINGLETHREAD
901 + this.EndFind(new object[] { nodePath, foundCell });
902 + #else
903 + this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodePath, foundCell } });
904 + #endif
905 + return;
906 + }
907 + }
908 + else
909 + {
910 + if(this.continueSearchRoot != null)
911 + {
912 + searchNode = this.FindNode(this.continueSearchRoot);
913 + startCell = this.continueSearchCell;
914 + endCell = this.continueSearchEndCell;
915 + }
916 + else
917 + {
918 + this.continueSearchRoot = searchNode.FullPath;
919 + this.continueSearchPath = this.continueSearchRoot;
920 + this.continueSearchEndCell = endCell;
921 + }
922 + //if(searchNode == null) return;
923 + ArrayList nodesList = new ArrayList();
924 + nodesList.Add(searchNode);
925 + this.GetFlatTreeNodes(searchNode.Nodes, nodesList, true, this.continueSearchRoot);
926 + TreeNode[] nodes = (TreeNode[]) nodesList.ToArray(typeof(TreeNode));
927 + int startNode = nodesList.IndexOf(this.FindNode(this.continueSearchPath));
928 + DataGridCell foundCell;
929 + startCell.ColumnNumber++;
930 + for(int i = startNode; i < nodes.Length; i++)
931 + {
932 + if(this.stopSearch) break;
933 + DataGridCell startCellOnThisNode = zeroCell;
934 + if(i == startNode) startCellOnThisNode = startCell;
935 + DataView table = this.GetTableForSearch(nodes[i].FullPath);
936 + if(table != null)
937 + {
938 + if(FindInTable(table, searchString, ignoreCase, startCellOnThisNode, zeroCell, false, out foundCell))
939 + {
940 + #if SINGLETHREAD
941 + this.EndFind(new object[] { nodes[i].FullPath, foundCell });
942 + #else
943 + this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodes[i].FullPath, foundCell } });
944 + #endif
945 + return;
946 + }
947 + }
948 + }
949 + if(!this.stopSearch)
950 + {
951 + DataView table = this.GetTableForSearch(searchNode.FullPath);
952 + if(table != null)
953 + {
954 + if(FindInTable(table, searchString, ignoreCase, zeroCell, endCell, false, out foundCell))
955 + {
956 + #if SINGLETHREAD
957 + this.EndFind(new object[] { searchNode.FullPath, foundCell });
958 + #else
959 + this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { searchNode.FullPath, foundCell } });
960 + #endif
961 + return;
962 + }
963 + }
964 + }
965 + }
966 + #if SINGLETHREAD
967 + this.EndFind(null);
968 + #else
969 + this.Invoke(new WaitCallback(this.EndFind), new object[] { null });
970 + #endif
971 + }
972 +
973 + private DataView GetTableForSearch(string nodePath)
974 + {
975 + DataView table = (DataView) this.data[nodePath];
976 + string status = nodePath;
977 + if(table == null) status = status + " (loading)";
978 + #if SINGLETHREAD
979 + this.FindStatus(nodePath);
980 + #else
981 + this.Invoke(new WaitCallback(this.FindStatus), new object[] { status });
982 + #endif
983 + if(table == null)
984 + {
985 + this.tablesLoading.Add(nodePath);
986 + this.Invoke(new ThreadStart(this.SetCursor));
987 + this.LoadTable(nodePath);
988 + table = (DataView) this.data[nodePath];
989 + }
990 + return table;
991 + }
992 +
993 + private void GetFlatTreeNodes(TreeNodeCollection nodes, IList resultsList, bool searchable, string searchRoot)
994 + {
995 + foreach(TreeNode node in nodes)
996 + {
997 + string nodePath = node.FullPath;
998 + IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
999 + if(!searchable || (dataProvider != null && dataProvider.IsNodeSearchable(searchRoot, nodePath)))
1000 + {
1001 + resultsList.Add(node);
1002 + }
1003 + GetFlatTreeNodes(node.Nodes, resultsList, searchable, searchRoot);
1004 + }
1005 + }
1006 +
1007 + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
1008 + private bool FindInTable(DataView table, string searchString, bool lowerCase,
1009 + DataGridCell startCell, DataGridCell endCell, bool wrap, out DataGridCell foundCell)
1010 + {
1011 + foundCell = new DataGridCell(-1, -1);
1012 + if(table == null) return false;
1013 + if(startCell.RowNumber < 0) startCell.RowNumber = 0;
1014 + if(startCell.ColumnNumber < 0) startCell.ColumnNumber = 0;
1015 + for(int searchRow = startCell.RowNumber; searchRow < table.Count; searchRow++)
1016 + {
1017 + if(this.stopSearch) break;
1018 + if(endCell.RowNumber > startCell.RowNumber && searchRow > endCell.RowNumber) break;
1019 +
1020 + DataRowView tableRow = table[searchRow];
1021 + for(int searchCol = (searchRow == startCell.RowNumber
1022 + ? startCell.ColumnNumber : 0); searchCol < table.Table.Columns.Count; searchCol++)
1023 + {
1024 + if(this.stopSearch) break;
1025 + if(endCell.RowNumber > startCell.RowNumber && searchRow == endCell.RowNumber
1026 + && searchCol >= endCell.ColumnNumber) break;
1027 +
1028 + string value = tableRow[searchCol].ToString();
1029 + if(lowerCase) value = value.ToLowerInvariant();
1030 + if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1031 + {
1032 + foundCell.RowNumber = searchRow;
1033 + foundCell.ColumnNumber = searchCol;
1034 + return true;
1035 + }
1036 + }
1037 + }
1038 + if(wrap)
1039 + {
1040 + for(int searchRow = 0; searchRow <= endCell.RowNumber; searchRow++)
1041 + {
1042 + if(this.stopSearch) break;
1043 + DataRowView tableRow = table[searchRow];
1044 + for(int searchCol = 0; searchCol < (searchRow == endCell.RowNumber
1045 + ? endCell.ColumnNumber : table.Table.Columns.Count); searchCol++)
1046 + {
1047 + if(this.stopSearch) break;
1048 + string value = tableRow[searchCol].ToString();
1049 + if(lowerCase) value = value.ToLowerInvariant();
1050 + if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1051 + {
1052 + foundCell.RowNumber = searchRow;
1053 + foundCell.ColumnNumber = searchCol;
1054 + return true;
1055 + }
1056 + }
1057 + }
1058 + }
1059 + return false;
1060 + }
1061 +
1062 + private void BeginFind()
1063 + {
1064 + lock(syncRoot)
1065 + {
1066 + this.findButton.Enabled = false;
1067 + this.findButton.Text = "Searching...";
1068 + this.findTextBox.Enabled = false;
1069 + this.searchTreeCheckBox.Visible = false;
1070 + this.findStopButton.Visible = true;
1071 + this.refreshButton.Enabled = false;
1072 + this.searching = true;
1073 + this.stopSearch = false;
1074 + this.SetCursor();
1075 + }
1076 + }
1077 +
1078 + private void FindStatus(object status)
1079 + {
1080 + lock(syncRoot)
1081 + {
1082 + this.dataGrid.CaptionText = "Searching... " + (string) status;
1083 + this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
1084 + this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
1085 + }
1086 + }
1087 +
1088 + private void EndFind(object result)
1089 + {
1090 + lock(syncRoot)
1091 + {
1092 + this.searching = false;
1093 + this.refreshButton.Enabled = true;
1094 + this.findStopButton.Visible = false;
1095 + this.searchTreeCheckBox.Visible = true;
1096 + this.findTextBox.Enabled = true;
1097 + this.findButton.Text = "Find";
1098 + this.findButton.Enabled = true;
1099 + this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
1100 + this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
1101 + this.dataGrid.CaptionText = this.CurrentNodePath;
1102 + if(result != null)
1103 + {
1104 + string nodePath = (string) ((object[]) result)[0];
1105 + DataGridCell foundCell = (DataGridCell) ((object[]) result)[1];
1106 + this.GoTo(nodePath, foundCell);
1107 + this.dataGrid.Focus();
1108 + this.continueSearchPath = nodePath;
1109 + this.continueSearchCell = foundCell;
1110 + if(this.searchTreeCheckBox.Checked) this.searchTreeCheckBox.Text = "Continue";
1111 + }
1112 + else
1113 + {
1114 + this.continueSearchRoot = null;
1115 + this.continueSearchPath = null;
1116 + this.searchTreeCheckBox.Text = "In Subtree";
1117 + }
1118 + this.SetCursor();
1119 + }
1120 + }
1121 +
1122 + private void SetCursor()
1123 + {
1124 + if(this.mouseOverGridLink)
1125 + {
1126 + Keys modKeys = Control.ModifierKeys;
1127 + if((modKeys & (Keys.Shift | Keys.Control)) == 0)
1128 + {
1129 + this.Cursor = Cursors.Hand;
1130 + return;
1131 + }
1132 + }
1133 + if(this.tablesLoading == null || this.tablesLoading.Count > 0 || this.searching)
1134 + {
1135 + this.Cursor = Cursors.AppStarting;
1136 + return;
1137 + }
1138 + this.Cursor = Cursors.Arrow;
1139 + }
1140 +
1141 + #endregion
1142 +
1143 + #region EventHandlers
1144 +
1145 + private void Inventory_Load(object sender, System.EventArgs e)
1146 + {
1147 + this.RefreshData();
1148 + }
1149 + private void refreshButton_Click(object sender, System.EventArgs e)
1150 + {
1151 + this.RefreshData();
1152 + }
1153 + private void Inventory_MouseDown(object sender, MouseEventArgs e)
1154 + {
1155 + if(e.Button == MouseButtons.XButton1) this.HistoryBack();
1156 + else if(e.Button == MouseButtons.XButton2) this.HistoryForward();
1157 + }
1158 + private void Inventory_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1159 + {
1160 + this.SetCursor();
1161 + if(e.KeyCode == Keys.F3) this.Find();
1162 + else if(e.KeyCode == Keys.F && (e.Modifiers | Keys.Control) != 0) this.findTextBox.Focus();
1163 + else if(e.KeyCode == Keys.BrowserBack) this.HistoryBack();
1164 + else if(e.KeyCode == Keys.BrowserForward) this.HistoryForward();
1165 + else return;
1166 + e.Handled = true;
1167 + }
1168 + private void treeView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1169 + {
1170 + this.Inventory_KeyDown(sender, e);
1171 + }
1172 + private void dataGrid_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1173 + {
1174 + this.Inventory_KeyDown(sender, e);
1175 + }
1176 + private void Inventory_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1177 + {
1178 + this.SetCursor();
1179 + }
1180 + private void treeView_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1181 + {
1182 + this.Inventory_KeyDown(sender, e);
1183 + }
1184 + private void dataGrid_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1185 + {
1186 + this.Inventory_KeyDown(sender, e);
1187 + }
1188 + private void treeView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
1189 + {
1190 + this.GoTo(e.Node.FullPath, anyCell);
1191 + }
1192 + private void backButton_Click(object sender, System.EventArgs e)
1193 + {
1194 + this.HistoryBack();
1195 + }
1196 + private void forwardButton_Click(object sender, System.EventArgs e)
1197 + {
1198 + this.HistoryForward();
1199 + }
1200 + private void findTextBox_TextChanged(object sender, System.EventArgs e)
1201 + {
1202 + this.findButton.Enabled = this.findTextBox.Text.Length > 0 &&
1203 + this.tablesLoading != null && this.treeView.SelectedNode != null && !searching;
1204 + this.searchTreeCheckBox.Text = "In Subtree";
1205 + this.continueSearchRoot = null;
1206 + }
1207 + private void findButton_Click(object sender, System.EventArgs e)
1208 + {
1209 + this.Find();
1210 + }
1211 + private void findTextBox_Enter(object sender, System.EventArgs e)
1212 + {
1213 + findTextBox.SelectAll();
1214 + }
1215 + private void findStopButton_Click(object sender, System.EventArgs e)
1216 + {
1217 + this.stopSearch = true;
1218 + }
1219 +
1220 + private void searchTreeCheckBox_CheckedChanged(object sender, System.EventArgs e)
1221 + {
1222 + if(!searchTreeCheckBox.Checked && searchTreeCheckBox.Text == "Continue")
1223 + {
1224 + this.searchTreeCheckBox.Text = "In Subtree";
1225 + }
1226 + }
1227 +
1228 + #endregion
1229 +
1230 + }
1231 +}
src/samples/Dtf/Inventory/Inventory.csproj new
+42
@@ -0,0 +1,42 @@
1 +
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 + <PropertyGroup>
7 + <ProjectGuid>{51480F8E-B80F-42DC-91E7-3542C1F12F8C}</ProjectGuid>
8 + <OutputType>WinExe</OutputType>
9 + <RootNamespace>WixToolset.Dtf.Samples.Inventory</RootNamespace>
10 + <AssemblyName>Inventory</AssemblyName>
11 + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 + <ApplicationIcon>Inventory.ico</ApplicationIcon>
13 + <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
14 + </PropertyGroup>
15 +
16 + <ItemGroup>
17 + <Compile Include="components.cs" />
18 + <Compile Include="Features.cs" />
19 + <Compile Include="IInventoryDataProvider.cs" />
20 + <Compile Include="Inventory.cs">
21 + <SubType>Form</SubType>
22 + </Compile>
23 + <Compile Include="msiutils.cs" />
24 + <Compile Include="patches.cs" />
25 + <Compile Include="products.cs" />
26 + </ItemGroup>
27 +
28 + <ItemGroup>
29 + <Content Include="Inventory.ico" />
30 + </ItemGroup>
31 +
32 + <ItemGroup>
33 + <Reference Include="System" />
34 + <Reference Include="System.Data" />
35 + <Reference Include="System.Drawing" />
36 + <Reference Include="System.Windows.Forms" />
37 + <Reference Include="System.Xml" />
38 + <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
39 + </ItemGroup>
40 +
41 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
42 +</Project>
src/samples/Dtf/Inventory/Inventory.ico
Binary files /dev/null and b/src/samples/Dtf/Inventory/Inventory.ico differ
src/samples/Dtf/Inventory/Inventory.resx new
+265
@@ -0,0 +1,265 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<root>
3 + <!--
4 + Microsoft ResX Schema
5 +
6 + Version 1.3
7 +
8 + The primary goals of this format is to allow a simple XML format
9 + that is mostly human readable. The generation and parsing of the
10 + various data types are done through the TypeConverter classes
11 + associated with the data types.
12 +
13 + Example:
14 +
15 + ... ado.net/XML headers & schema ...
16 + <resheader name="resmimetype">text/microsoft-resx</resheader>
17 + <resheader name="version">1.3</resheader>
18 + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 + <data name="Name1">this is my long string</data>
21 + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 + [base64 mime encoded serialized .NET Framework object]
24 + </data>
25 + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 + [base64 mime encoded string representing a byte array form of the .NET Framework object]
27 + </data>
28 +
29 + There are any number of "resheader" rows that contain simple
30 + name/value pairs.
31 +
32 + Each data row contains a name, and value. The row also contains a
33 + type or mimetype. Type corresponds to a .NET class that support
34 + text/value conversion through the TypeConverter architecture.
35 + Classes that don't support this are serialized and stored with the
36 + mimetype set.
37 +
38 + The mimetype is used forserialized objects, and tells the
39 + ResXResourceReader how to depersist the object. This is currently not
40 + extensible. For a given mimetype the value must be set accordingly:
41 +
42 + Note - application/x-microsoft.net.object.binary.base64 is the format
43 + that the ResXResourceWriter will generate, however the reader can
44 + read any of the formats listed below.
45 +
46 + mimetype: application/x-microsoft.net.object.binary.base64
47 + value : The object must be serialized with
48 + : System.Serialization.Formatters.Binary.BinaryFormatter
49 + : and then encoded with base64 encoding.
50 +
51 + mimetype: application/x-microsoft.net.object.soap.base64
52 + value : The object must be serialized with
53 + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
54 + : and then encoded with base64 encoding.
55 +
56 + mimetype: application/x-microsoft.net.object.bytearray.base64
57 + value : The object must be serialized into a byte array
58 + : using a System.ComponentModel.TypeConverter
59 + : and then encoded with base64 encoding.
60 + -->
61 + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
62 + <xsd:element name="root" msdata:IsDataSet="true">
63 + <xsd:complexType>
64 + <xsd:choice maxOccurs="unbounded">
65 + <xsd:element name="data">
66 + <xsd:complexType>
67 + <xsd:sequence>
68 + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
69 + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
70 + </xsd:sequence>
71 + <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
72 + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
73 + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
74 + </xsd:complexType>
75 + </xsd:element>
76 + <xsd:element name="resheader">
77 + <xsd:complexType>
78 + <xsd:sequence>
79 + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
80 + </xsd:sequence>
81 + <xsd:attribute name="name" type="xsd:string" use="required" />
82 + </xsd:complexType>
83 + </xsd:element>
84 + </xsd:choice>
85 + </xsd:complexType>
86 + </xsd:element>
87 + </xsd:schema>
88 + <resheader name="resmimetype">
89 + <value>text/microsoft-resx</value>
90 + </resheader>
91 + <resheader name="version">
92 + <value>1.3</value>
93 + </resheader>
94 + <resheader name="reader">
95 + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
96 + </resheader>
97 + <resheader name="writer">
98 + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
99 + </resheader>
100 + <data name="dataGrid.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
101 + <value>False</value>
102 + </data>
103 + <data name="dataGrid.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
104 + <value>Private</value>
105 + </data>
106 + <data name="dataGrid.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
107 + <value>Private</value>
108 + </data>
109 + <data name="treeView.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
110 + <value>Private</value>
111 + </data>
112 + <data name="treeView.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
113 + <value>Private</value>
114 + </data>
115 + <data name="treeView.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
116 + <value>False</value>
117 + </data>
118 + <data name="toolPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
119 + <value>False</value>
120 + </data>
121 + <data name="toolPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
122 + <value>True</value>
123 + </data>
124 + <data name="toolPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
125 + <value>True</value>
126 + </data>
127 + <data name="toolPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
128 + <value>Private</value>
129 + </data>
130 + <data name="toolPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
131 + <value>Private</value>
132 + </data>
133 + <data name="toolPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
134 + <value>8, 8</value>
135 + </data>
136 + <data name="findStopButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
137 + <value>False</value>
138 + </data>
139 + <data name="findStopButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
140 + <value>Private</value>
141 + </data>
142 + <data name="findStopButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
143 + <value>Private</value>
144 + </data>
145 + <data name="findButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
146 + <value>False</value>
147 + </data>
148 + <data name="findButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
149 + <value>Private</value>
150 + </data>
151 + <data name="findButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
152 + <value>Private</value>
153 + </data>
154 + <data name="searchTreeCheckBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
155 + <value>False</value>
156 + </data>
157 + <data name="searchTreeCheckBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
158 + <value>Private</value>
159 + </data>
160 + <data name="searchTreeCheckBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
161 + <value>Private</value>
162 + </data>
163 + <data name="findTextBox.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
164 + <value>Private</value>
165 + </data>
166 + <data name="findTextBox.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
167 + <value>False</value>
168 + </data>
169 + <data name="findTextBox.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
170 + <value>Private</value>
171 + </data>
172 + <data name="refreshButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
173 + <value>False</value>
174 + </data>
175 + <data name="refreshButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
176 + <value>Private</value>
177 + </data>
178 + <data name="refreshButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
179 + <value>Private</value>
180 + </data>
181 + <data name="forwardButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
182 + <value>False</value>
183 + </data>
184 + <data name="forwardButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
185 + <value>Private</value>
186 + </data>
187 + <data name="forwardButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
188 + <value>Private</value>
189 + </data>
190 + <data name="backButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
191 + <value>False</value>
192 + </data>
193 + <data name="backButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
194 + <value>Private</value>
195 + </data>
196 + <data name="backButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
197 + <value>Private</value>
198 + </data>
199 + <data name="dataPanel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
200 + <value>False</value>
201 + </data>
202 + <data name="dataPanel.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
203 + <value>True</value>
204 + </data>
205 + <data name="dataPanel.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
206 + <value>True</value>
207 + </data>
208 + <data name="dataPanel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
209 + <value>Private</value>
210 + </data>
211 + <data name="dataPanel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
212 + <value>Private</value>
213 + </data>
214 + <data name="dataPanel.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
215 + <value>8, 8</value>
216 + </data>
217 + <data name="splitter.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
218 + <value>False</value>
219 + </data>
220 + <data name="splitter.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
221 + <value>Private</value>
222 + </data>
223 + <data name="splitter.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
224 + <value>Private</value>
225 + </data>
226 + <data name="gridLinkTip.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
227 + <value>Private</value>
228 + </data>
229 + <data name="gridLinkTip.Location" type="System.Drawing.Point, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
230 + <value>17, 17</value>
231 + </data>
232 + <data name="gridLinkTip.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
233 + <value>Private</value>
234 + </data>
235 + <data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
236 + <value>False</value>
237 + </data>
238 + <data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
239 + <value>(Default)</value>
240 + </data>
241 + <data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
242 + <value>False</value>
243 + </data>
244 + <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
245 + <value>False</value>
246 + </data>
247 + <data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
248 + <value>8, 8</value>
249 + </data>
250 + <data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
251 + <value>True</value>
252 + </data>
253 + <data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
254 + <value>80</value>
255 + </data>
256 + <data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
257 + <value>True</value>
258 + </data>
259 + <data name="$this.Name">
260 + <value>Inventory</value>
261 + </data>
262 + <data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
263 + <value>Private</value>
264 + </data>
265 +</root>
\ No newline at end of file
src/samples/Dtf/Inventory/components.cs new
+626
@@ -0,0 +1,626 @@
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 +using System;
4 +using System.IO;
5 +using System.Data;
6 +using System.Text;
7 +using System.Collections;
8 +using System.Collections.Generic;
9 +using System.Globalization;
10 +using System.Windows.Forms;
11 +using Microsoft.Win32;
12 +using WixToolset.Dtf.WindowsInstaller;
13 +using View = WixToolset.Dtf.WindowsInstaller.View;
14 +
15 +namespace WixToolset.Dtf.Samples.Inventory
16 +{
17 + /// <summary>
18 + /// Provides inventory data about components of products installed on the system.
19 + /// </summary>
20 + public class ComponentsInventory : IInventoryDataProvider
21 + {
22 + private static object syncRoot = new object();
23 +
24 + public ComponentsInventory()
25 + {
26 + }
27 +
28 + public string Description
29 + {
30 + get { return "Components of installed products"; }
31 + }
32 +
33 + private Hashtable componentProductsMap;
34 +
35 + public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
36 + {
37 + ArrayList nodes = new ArrayList();
38 + componentProductsMap = new Hashtable();
39 + foreach(ProductInstallation product in ProductInstallation.AllProducts)
40 + {
41 + string productName = MsiUtils.GetProductName(product.ProductCode);
42 + statusCallback(nodes.Count, String.Format(@"Products\{0}", productName));
43 +
44 + try
45 + {
46 + IntPtr hWnd = IntPtr.Zero;
47 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
48 + lock(syncRoot) // Only one Installer session can be active at a time
49 + {
50 + using (Session session = Installer.OpenProduct(product.ProductCode))
51 + {
52 + statusCallback(nodes.Count, String.Format(@"Products\{0}\Features", productName));
53 + IList<string> features = session.Database.ExecuteStringQuery("SELECT `Feature` FROM `Feature`");
54 + string[] featuresArray = new string[features.Count];
55 + features.CopyTo(featuresArray, 0);
56 + Array.Sort(featuresArray, 0, featuresArray.Length, StringComparer.OrdinalIgnoreCase);
57 + foreach (string feature in featuresArray)
58 + {
59 + nodes.Add(String.Format(@"Products\{0}\Features\{1}", productName, feature));
60 + }
61 + statusCallback(nodes.Count, String.Format(@"Products\{0}\Components", productName));
62 + nodes.Add(String.Format(@"Products\{0}\Components", productName));
63 + IList<string> components = session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`");
64 + for (int i = 0; i < components.Count; i++)
65 + {
66 + string component = components[i];
67 + if (component.Length > 0)
68 + {
69 + nodes.Add(String.Format(@"Products\{0}\Components\{1}", productName, component));
70 + ArrayList sharingProducts = (ArrayList) componentProductsMap[component];
71 + if (sharingProducts == null)
72 + {
73 + sharingProducts = new ArrayList();
74 + componentProductsMap[component] = sharingProducts;
75 + }
76 + sharingProducts.Add(product.ProductCode);
77 + }
78 + if (i % 100 == 0) statusCallback(nodes.Count, null);
79 + }
80 + nodes.Add(String.Format(@"Products\{0}\Files", productName));
81 + nodes.Add(String.Format(@"Products\{0}\Registry", productName));
82 + statusCallback(nodes.Count, String.Empty);
83 + }
84 + }
85 + }
86 + catch(InstallerException) { }
87 + }
88 + statusCallback(nodes.Count, @"Products\...\Components\...\Sharing");
89 + foreach (DictionaryEntry componentProducts in componentProductsMap)
90 + {
91 + string component = (string) componentProducts.Key;
92 + ArrayList products = (ArrayList) componentProducts.Value;
93 + if(products.Count > 1)
94 + {
95 + foreach(string productCode in products)
96 + {
97 + nodes.Add(String.Format(@"Products\{0}\Components\{1}\Sharing", MsiUtils.GetProductName(productCode), component));
98 + }
99 + }
100 + }
101 + statusCallback(nodes.Count, String.Empty);
102 + return (string[]) nodes.ToArray(typeof(string));
103 + }
104 +
105 + public bool IsNodeSearchable(string searchRoot, string searchNode)
106 + {
107 + string[] rootPath = searchRoot.Split('\\');
108 + string[] nodePath = searchNode.Split('\\');
109 + if(rootPath.Length < 3 && nodePath.Length >= 3 && nodePath[0] == "Products" && nodePath[2] == "Components")
110 + {
111 + // When searching an entire product, don't search the "Components" subtree --
112 + // it just has duplicate data from the Files and Registry table. And if you
113 + // really want to know about the component, it's only a click away from
114 + // those other tables.
115 + return false;
116 + }
117 + return true;
118 + }
119 +
120 + public DataView GetData(string nodePath)
121 + {
122 + string[] path = nodePath.Split('\\');
123 +
124 + if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
125 + {
126 + return GetFeatureComponentsData(MsiUtils.GetProductCode(path[1]), path[3]);
127 + }
128 + else if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
129 + {
130 + return GetProductComponentsData(MsiUtils.GetProductCode(path[1]));
131 + }
132 + else if(path.Length == 4 && path[0] == "Products" && path[2] == "Components")
133 + {
134 + return GetComponentData(MsiUtils.GetProductCode(path[1]), path[3]);
135 + }
136 + else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
137 + {
138 + return GetComponentProductsData(path[3]);
139 + }
140 + else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
141 + {
142 + return GetProductFilesData(MsiUtils.GetProductCode(path[1]));
143 + }
144 + else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
145 + {
146 + return GetProductRegistryData(MsiUtils.GetProductCode(path[1]));
147 + }
148 + return null;
149 + }
150 +
151 + public DataView GetComponentData(string productCode, string componentCode)
152 + {
153 + DataTable table = new DataTable("ProductComponentItems");
154 + table.Locale = CultureInfo.InvariantCulture;
155 + table.Columns.Add("ProductComponentItemsIsKey", typeof(bool));
156 + table.Columns.Add("ProductComponentItemsKey", typeof(string));
157 + table.Columns.Add("ProductComponentItemsPath", typeof(string));
158 + table.Columns.Add("ProductComponentItemsExists", typeof(bool));
159 + table.Columns.Add("ProductComponentItemsDbVersion", typeof(string));
160 + table.Columns.Add("ProductComponentItemsInstalledVersion", typeof(string));
161 + table.Columns.Add("ProductComponentItemsInstalledMatch", typeof(bool));
162 + try
163 + {
164 + IntPtr hWnd = IntPtr.Zero;
165 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
166 + lock(syncRoot) // Only one Installer session can be active at a time
167 + {
168 + using(Session session = Installer.OpenProduct(productCode))
169 + {
170 + session.DoAction("CostInitialize");
171 + session.DoAction("FileCost");
172 + session.DoAction("CostFinalize");
173 +
174 + foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, false))
175 + {
176 + table.Rows.Add(row);
177 + }
178 + foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, false))
179 + {
180 + table.Rows.Add(row);
181 + }
182 + }
183 + }
184 + return new DataView(table, "", "ProductComponentItemsPath ASC", DataViewRowState.CurrentRows);
185 + }
186 + catch(InstallerException) { }
187 + return null;
188 + }
189 +
190 + private object[][] GetComponentFilesRows(string productCode, string componentCode, Session session, bool includeComponent)
191 + {
192 + ArrayList rows = new ArrayList();
193 + string componentPath = new ComponentInstallation(componentCode, productCode).Path;
194 +
195 + string componentKey = (string) session.Database.ExecuteScalar(
196 + "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
197 + if(componentKey == null) return null;
198 + int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
199 + "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
200 + bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
201 + if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
202 + string keyPath = (string) session.Database.ExecuteScalar(
203 + "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
204 +
205 + using (View view = session.Database.OpenView("SELECT `File`, `FileName`, `Version`, `Language`, " +
206 + "`Attributes` FROM `File` WHERE `Component_` = '{0}'", componentKey))
207 + {
208 + view.Execute();
209 +
210 + foreach (Record rec in view) using (rec)
211 + {
212 + string fileKey = (string) rec["File"];
213 + bool isKey = !registryKeyPath && keyPath == fileKey;
214 +
215 + string dbVersion = (string) rec["Version"];
216 + bool versionedFile = dbVersion.Length != 0;
217 + if(versionedFile)
218 + {
219 + string language = (string) rec["Language"];
220 + if(language.Length > 0)
221 + {
222 + dbVersion = dbVersion + " (" + language + ")";
223 + }
224 + }
225 + else if(session.Database.Tables.Contains("MsiFileHash"))
226 + {
227 + IList<int> hash = session.Database.ExecuteIntegerQuery("SELECT `HashPart1`, `HashPart2`, " +
228 + "`HashPart3`, `HashPart4` FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey);
229 + if(hash != null && hash.Count == 4)
230 + {
231 + dbVersion = this.GetFileHashString(hash);
232 + }
233 + }
234 +
235 + string filePath = GetLongFileName((string) rec["FileName"]);
236 + bool exists = false;
237 + bool installedMatch = false;
238 + string installedVersion = "";
239 + if(!registryKeyPath && componentPath.Length > 0)
240 + {
241 + filePath = Path.Combine(componentPath, filePath);
242 +
243 + if(File.Exists(filePath))
244 + {
245 + exists = true;
246 + if(versionedFile)
247 + {
248 + installedVersion = Installer.GetFileVersion(filePath);
249 + string language = Installer.GetFileLanguage(filePath);
250 + if(language.Length > 0)
251 + {
252 + installedVersion = installedVersion + " (" + language + ")";
253 + }
254 + }
255 + else
256 + {
257 + int[] hash = new int[4];
258 + Installer.GetFileHash(filePath, hash);
259 + installedVersion = this.GetFileHashString(hash);
260 + }
261 + installedMatch = installedVersion == dbVersion;
262 + }
263 + }
264 +
265 + object[] row;
266 + if(includeComponent) row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch, componentCode };
267 + else row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch };
268 + rows.Add(row);
269 + }
270 + }
271 +
272 + return (object[][]) rows.ToArray(typeof(object[]));
273 + }
274 +
275 + private string GetLongFileName(string fileName)
276 + {
277 + string[] fileNames = fileName.Split('|');
278 + return fileNames.Length == 1? fileNames[0] : fileNames[1];
279 + }
280 +
281 + private string GetFileHashString(IList<int> hash)
282 + {
283 + return String.Format("{0:X8}{1:X8}{2:X8}{3:X8}", (uint) hash[0], (uint) hash[1], (uint) hash[2], (uint) hash[3]);
284 + }
285 +
286 + private object[][] GetComponentRegistryRows(string productCode, string componentCode, Session session, bool includeComponent)
287 + {
288 + ArrayList rows = new ArrayList();
289 + string componentPath = new ComponentInstallation(componentCode, productCode).Path;
290 +
291 + string componentKey = (string) session.Database.ExecuteScalar(
292 + "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
293 + if(componentKey == null) return null;
294 + int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
295 + "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
296 + bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
297 + if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
298 + string keyPath = (string) session.Database.ExecuteScalar(
299 + "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
300 +
301 + using (View view = session.Database.OpenView("SELECT `Registry`, `Root`, `Key`, `Name`, " +
302 + "`Value` FROM `Registry` WHERE `Component_` = '{0}'", componentKey))
303 + {
304 + view.Execute();
305 +
306 + foreach (Record rec in view) using (rec)
307 + {
308 + string regName = (string) rec["Name"];
309 + if(regName == "-") continue; // Don't list deleted keys
310 +
311 + string regTableKey = (string) rec["Registry"];
312 + bool isKey = registryKeyPath && keyPath == regTableKey;
313 + string regPath = this.GetRegistryPath(session, (RegistryRoot) Convert.ToInt32(rec["Root"]),
314 + (string) rec["Key"], (string) rec["Name"]);
315 +
316 + string dbValue;
317 + using(Record formatRec = new Record(0))
318 + {
319 + formatRec[0] = rec["Value"];
320 + dbValue = session.FormatRecord(formatRec);
321 + }
322 +
323 + string installedValue = this.GetRegistryValue(regPath);
324 + bool exists = installedValue != null;
325 + if(!exists) installedValue = "";
326 + bool match = installedValue == dbValue;
327 +
328 + object[] row;
329 + if(includeComponent) row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match, componentCode };
330 + else row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match };
331 + rows.Add(row);
332 + }
333 + }
334 +
335 + return (object[][]) rows.ToArray(typeof(object[]));
336 + }
337 +
338 + private string GetRegistryPath(Session session, RegistryRoot root, string key, string name)
339 + {
340 + bool allUsers = session.EvaluateCondition("ALLUSERS = 1", true);
341 + string rootName = "????";
342 + switch(root)
343 + {
344 + case RegistryRoot.LocalMachine : rootName = "HKLM"; break;
345 + case RegistryRoot.CurrentUser : rootName = "HKCU"; break;
346 + case RegistryRoot.Users : rootName = "HKU"; break;
347 + case RegistryRoot.UserOrMachine: rootName = (allUsers ? "HKLM" : "HKCU"); break;
348 + case RegistryRoot.ClassesRoot : rootName = (allUsers ? @"HKLM\Software\Classes" : @"HKCU\Software\Classes"); break;
349 + // TODO: Technically, RegistryRoot.ClassesRoot should be under HKLM on NT4.
350 + }
351 + if(name.Length == 0) name = "(Default)";
352 + if(name == "+" || name == "*") name = "";
353 + else name = " : " + name;
354 + using(Record formatRec = new Record(0))
355 + {
356 + formatRec[0] = String.Format(@"{0}\{1}{2}", rootName, key, name);
357 + return session.FormatRecord(formatRec);
358 + }
359 + }
360 +
361 + private string GetRegistryValue(string regPath)
362 + {
363 + string valueName = null;
364 + int iColon = regPath.IndexOf(" : ", StringComparison.Ordinal) + 1;
365 + if(iColon > 0)
366 + {
367 + valueName = regPath.Substring(iColon + 2);
368 + regPath = regPath.Substring(0, iColon - 1);
369 + }
370 + if(valueName == "(Default)") valueName = "";
371 +
372 + RegistryKey root;
373 + if(regPath.StartsWith(@"HKLM\", StringComparison.Ordinal))
374 + {
375 + root = Registry.LocalMachine;
376 + regPath = regPath.Substring(5);
377 + }
378 + else if(regPath.StartsWith(@"HKCU\", StringComparison.Ordinal))
379 + {
380 + root = Registry.CurrentUser;
381 + regPath = regPath.Substring(5);
382 + }
383 + else if(regPath.StartsWith(@"HKU\", StringComparison.Ordinal))
384 + {
385 + root = Registry.Users;
386 + regPath = regPath.Substring(4);
387 + }
388 + else return null;
389 +
390 + using(RegistryKey regKey = root.OpenSubKey(regPath))
391 + {
392 + if(regKey != null)
393 + {
394 + if(valueName == null)
395 + {
396 + // Just checking for the existence of the key.
397 + return "";
398 + }
399 + object value = regKey.GetValue(valueName);
400 + if(value is string[])
401 + {
402 + value = String.Join("[~]", (string[]) value);
403 + }
404 + else if(value is int)
405 + {
406 + value = "#" + value.ToString();
407 + }
408 + else if(value is byte[])
409 + {
410 + byte[] valueBytes = (byte[]) value;
411 + StringBuilder byteString = new StringBuilder("#x");
412 + for(int i = 0; i < valueBytes.Length; i++)
413 + {
414 + byteString.Append(valueBytes[i].ToString("x2"));
415 + }
416 + value = byteString.ToString();
417 + }
418 + return (value != null ? value.ToString() : null);
419 + }
420 + }
421 + return null;
422 + }
423 +
424 + public DataView GetProductFilesData(string productCode)
425 + {
426 + DataTable table = new DataTable("ProductFiles");
427 + table.Locale = CultureInfo.InvariantCulture;
428 + table.Columns.Add("ProductFilesIsKey", typeof(bool));
429 + table.Columns.Add("ProductFilesKey", typeof(string));
430 + table.Columns.Add("ProductFilesPath", typeof(string));
431 + table.Columns.Add("ProductFilesExists", typeof(bool));
432 + table.Columns.Add("ProductFilesDbVersion", typeof(string));
433 + table.Columns.Add("ProductFilesInstalledVersion", typeof(string));
434 + table.Columns.Add("ProductFilesInstalledMatch", typeof(bool));
435 + table.Columns.Add("ProductFilesComponentID", typeof(string));
436 + try
437 + {
438 + IntPtr hWnd = IntPtr.Zero;
439 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
440 + lock(syncRoot) // Only one Installer session can be active at a time
441 + {
442 + using(Session session = Installer.OpenProduct(productCode))
443 + {
444 + session.DoAction("CostInitialize");
445 + session.DoAction("FileCost");
446 + session.DoAction("CostFinalize");
447 +
448 + foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
449 + {
450 + foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, true))
451 + {
452 + table.Rows.Add(row);
453 + }
454 + }
455 + }
456 + }
457 + return new DataView(table, "", "ProductFilesPath ASC", DataViewRowState.CurrentRows);
458 + }
459 + catch(InstallerException) { }
460 + return null;
461 + }
462 +
463 + public DataView GetProductRegistryData(string productCode)
464 + {
465 + DataTable table = new DataTable("ProductRegistry");
466 + table.Locale = CultureInfo.InvariantCulture;
467 + table.Columns.Add("ProductRegistryIsKey", typeof(bool));
468 + table.Columns.Add("ProductRegistryKey", typeof(string));
469 + table.Columns.Add("ProductRegistryPath", typeof(string));
470 + table.Columns.Add("ProductRegistryExists", typeof(bool));
471 + table.Columns.Add("ProductRegistryDbVersion", typeof(string));
472 + table.Columns.Add("ProductRegistryInstalledVersion", typeof(string));
473 + table.Columns.Add("ProductRegistryInstalledMatch", typeof(bool));
474 + table.Columns.Add("ProductRegistryComponentID", typeof(string));
475 + try
476 + {
477 + IntPtr hWnd = IntPtr.Zero;
478 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
479 + lock(syncRoot) // Only one Installer session can be active at a time
480 + {
481 + using(Session session = Installer.OpenProduct(productCode))
482 + {
483 + session.DoAction("CostInitialize");
484 + session.DoAction("FileCost");
485 + session.DoAction("CostFinalize");
486 +
487 + foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
488 + {
489 + foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, true))
490 + {
491 + table.Rows.Add(row);
492 + }
493 + }
494 + }
495 + }
496 + return new DataView(table, "", "ProductRegistryPath ASC", DataViewRowState.CurrentRows);
497 + }
498 + catch(InstallerException) { }
499 + return null;
500 + }
501 +
502 + public DataView GetComponentProductsData(string componentCode)
503 + {
504 + DataTable table = new DataTable("ComponentProducts");
505 + table.Locale = CultureInfo.InvariantCulture;
506 + table.Columns.Add("ComponentProductsProductName", typeof(string));
507 + table.Columns.Add("ComponentProductsProductCode", typeof(string));
508 + table.Columns.Add("ComponentProductsComponentPath", typeof(string));
509 +
510 + if(this.componentProductsMap != null)
511 + {
512 + ArrayList componentProducts = (ArrayList) this.componentProductsMap[componentCode];
513 + foreach(string productCode in componentProducts)
514 + {
515 + string productName = MsiUtils.GetProductName(productCode);
516 + string componentPath = new ComponentInstallation(componentCode, productCode).Path;
517 + table.Rows.Add(new object[] { productName, productCode, componentPath });
518 + }
519 + return new DataView(table, "", "ComponentProductsProductName ASC", DataViewRowState.CurrentRows);
520 + }
521 + return null;
522 + }
523 +
524 + public DataView GetProductComponentsData(string productCode)
525 + {
526 + DataTable table = new DataTable("ProductComponents");
527 + table.Locale = CultureInfo.InvariantCulture;
528 + table.Columns.Add("ProductComponentsComponentName", typeof(string));
529 + table.Columns.Add("ProductComponentsComponentID", typeof(string));
530 + table.Columns.Add("ProductComponentsInstallState", typeof(string));
531 +
532 + try
533 + {
534 + IntPtr hWnd = IntPtr.Zero;
535 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
536 + lock(syncRoot) // Only one Installer session can be active at a time
537 + {
538 + using(Session session = Installer.OpenProduct(productCode))
539 + {
540 + session.DoAction("CostInitialize");
541 + session.DoAction("FileCost");
542 + session.DoAction("CostFinalize");
543 +
544 + IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
545 + "SELECT `Component`, `ComponentId` FROM `Component`");
546 +
547 + for (int i = 0; i < componentsAndIds.Count; i += 2)
548 + {
549 + if(componentsAndIds[i+1] == "Temporary Id") continue;
550 + InstallState compState = session.Components[componentsAndIds[i]].CurrentState;
551 + table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1],
552 + (compState == InstallState.Advertised ? "Advertised" : compState.ToString())});
553 + }
554 + }
555 + }
556 + return new DataView(table, "", "ProductComponentsComponentName ASC", DataViewRowState.CurrentRows);
557 + }
558 + catch(InstallerException) { }
559 + return null;
560 + }
561 +
562 + public DataView GetFeatureComponentsData(string productCode, string feature)
563 + {
564 + DataTable table = new DataTable("ProductFeatureComponents");
565 + table.Locale = CultureInfo.InvariantCulture;
566 + table.Columns.Add("ProductFeatureComponentsComponentName", typeof(string));
567 + table.Columns.Add("ProductFeatureComponentsComponentID", typeof(string));
568 +
569 + try
570 + {
571 + IntPtr hWnd = IntPtr.Zero;
572 + Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
573 + lock(syncRoot) // Only one Installer session can be active at a time
574 + {
575 + using(Session session = Installer.OpenProduct(productCode))
576 + {
577 + IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
578 + "SELECT `FeatureComponents`.`Component_`, " +
579 + "`Component`.`ComponentId` FROM `FeatureComponents`, `Component` " +
580 + "WHERE `FeatureComponents`.`Component_` = `Component`.`Component` " +
581 + "AND `FeatureComponents`.`Feature_` = '{0}'", feature);
582 + for (int i = 0; i < componentsAndIds.Count; i += 2)
583 + {
584 + table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1] });
585 + }
586 + }
587 + }
588 + return new DataView(table, "", "ProductFeatureComponentsComponentName ASC", DataViewRowState.CurrentRows);
589 + }
590 + catch(InstallerException) { }
591 + return null;
592 + }
593 +
594 + public string GetLink(string nodePath, DataRow row)
595 + {
596 + string[] path = nodePath.Split('\\');
597 +
598 + if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
599 + {
600 + string component = (string) row["ProductComponentsComponentID"];
601 + return String.Format(@"Products\{0}\Components\{1}", path[1], component);
602 + }
603 + else if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
604 + {
605 + string component = (string) row["ProductFeatureComponentsComponentID"];
606 + return String.Format(@"Products\{0}\Components\{1}", path[1], component);
607 + }
608 + else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
609 + {
610 + string component = (string) row["ProductFilesComponentID"];
611 + return String.Format(@"Products\{0}\Components\{1}", path[1], component);
612 + }
613 + else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
614 + {
615 + string component = (string) row["ProductRegistryComponentID"];
616 + return String.Format(@"Products\{0}\Components\{1}", path[1], component);
617 + }
618 + else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
619 + {
620 + string product = (string) row["ComponentProductsProductCode"];
621 + return String.Format(@"Products\{0}\Components\{1}", MsiUtils.GetProductName(product), path[3]);
622 + }
623 + return null;
624 + }
625 + }
626 +}
src/samples/Dtf/Inventory/msiutils.cs new
+46
@@ -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 +
3 +using System;
4 +using System.Collections;
5 +using WixToolset.Dtf.WindowsInstaller;
6 +
7 +
8 +namespace WixToolset.Dtf.Samples.Inventory
9 +{
10 + public class MsiUtils
11 + {
12 + private static Hashtable productCodesToNames = new Hashtable();
13 + private static Hashtable productNamesToCodes = new Hashtable();
14 +
15 + public static string GetProductName(string productCode)
16 + {
17 + string productName = (string) productCodesToNames[productCode];
18 + if(productName == null)
19 + {
20 + productName = new ProductInstallation(productCode).ProductName;
21 + productName = productName.Replace('\\', ' ');
22 + if(productNamesToCodes.Contains(productName))
23 + {
24 + string modifiedProductName = null;
25 + for(int i = 2; i < Int32.MaxValue; i++)
26 + {
27 + modifiedProductName = productName + " [" + i + "]";
28 + if(!productNamesToCodes.Contains(modifiedProductName)) break;
29 + }
30 + productName = modifiedProductName;
31 + }
32 + productCodesToNames[productCode] = productName;
33 + productNamesToCodes[productName] = productCode;
34 + }
35 + return productName;
36 + }
37 +
38 + // Assumes GetProductName() has already been called for this product.
39 + public static string GetProductCode(string productName)
40 + {
41 + return (string) productNamesToCodes[productName];
42 + }
43 +
44 + private MsiUtils() { }
45 + }
46 +}
src/samples/Dtf/Inventory/patches.cs new
+227
@@ -0,0 +1,227 @@
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 +using System;
4 +using System.IO;
5 +using System.Security;
6 +using System.Data;
7 +using System.Collections;
8 +using System.Globalization;
9 +using System.Windows.Forms;
10 +using WixToolset.Dtf.WindowsInstaller;
11 +
12 +namespace WixToolset.Dtf.Samples.Inventory
13 +{
14 + /// <summary>
15 + /// Provides inventory data about patches installed on the system.
16 + /// </summary>
17 + public class PatchesInventory : IInventoryDataProvider
18 + {
19 + public PatchesInventory()
20 + {
21 + }
22 +
23 + public string Description
24 + {
25 + get { return "Installed patches"; }
26 + }
27 +
28 + public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
29 + {
30 + ArrayList nodes = new ArrayList();
31 + statusCallback(nodes.Count, @"Products\...\Patches");
32 + foreach (ProductInstallation product in ProductInstallation.AllProducts)
33 + {
34 + string productName = MsiUtils.GetProductName(product.ProductCode);
35 +
36 + bool addedRoot = false;
37 + foreach (PatchInstallation productPatch in PatchInstallation.GetPatches(null, product.ProductCode, null, UserContexts.All, PatchStates.Applied))
38 + {
39 + if (!addedRoot) nodes.Add(String.Format(@"Products\{0}\Patches", productName));
40 + nodes.Add(String.Format(@"Products\{0}\Patches\{1}", productName, productPatch.PatchCode));
41 + }
42 + }
43 +
44 + statusCallback(nodes.Count, "Patches");
45 +
46 + string[] allPatches = GetAllPatchesList();
47 + if(allPatches.Length > 0)
48 + {
49 + nodes.Add("Patches");
50 + foreach(string patchCode in allPatches)
51 + {
52 + nodes.Add(String.Format(@"Patches\{0}", patchCode));
53 + nodes.Add(String.Format(@"Patches\{0}\Patched Products", patchCode));
54 + }
55 + statusCallback(nodes.Count, String.Empty);
56 + }
57 + return (string[]) nodes.ToArray(typeof(string));
58 + }
59 +
60 + public bool IsNodeSearchable(string searchRoot, string searchNode)
61 + {
62 + return true;
63 + }
64 +
65 + public DataView GetData(string nodePath)
66 + {
67 + string[] path = nodePath.Split('\\');
68 +
69 + if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
70 + {
71 + return this.GetProductPatchData(path[1]);
72 + }
73 + else if(path.Length == 4 && path[0] == "Products" && path[2] == "Patches")
74 + {
75 + return this.GetPatchData(path[3]);
76 + }
77 + else if(path.Length == 1 && path[0] == "Patches")
78 + {
79 + return this.GetAllPatchesData();
80 + }
81 + else if(path.Length == 2 && path[0] == "Patches")
82 + {
83 + return this.GetPatchData(path[1]);
84 + }
85 + else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
86 + {
87 + return this.GetPatchTargetData(path[1]);
88 + }
89 + return null;
90 + }
91 +
92 + private string[] GetAllPatchesList()
93 + {
94 + ArrayList patchList = new ArrayList();
95 + foreach(PatchInstallation patch in PatchInstallation.AllPatches)
96 + {
97 + if(!patchList.Contains(patch.PatchCode))
98 + {
99 + patchList.Add(patch.PatchCode);
100 + }
101 + }
102 + string[] patchArray = (string[]) patchList.ToArray(typeof(string));
103 + Array.Sort(patchArray, 0, patchArray.Length, StringComparer.Ordinal);
104 + return patchArray;
105 + }
106 +
107 + private DataView GetAllPatchesData()
108 + {
109 + DataTable table = new DataTable("Patches");
110 + table.Locale = CultureInfo.InvariantCulture;
111 + table.Columns.Add("PatchesPatchCode", typeof(string));
112 +
113 + foreach(string patchCode in GetAllPatchesList())
114 + {
115 + table.Rows.Add(new object[] { patchCode });
116 + }
117 + return new DataView(table, "", "PatchesPatchCode ASC", DataViewRowState.CurrentRows);
118 + }
119 +
120 + private DataView GetProductPatchData(string productCode)
121 + {
122 + DataTable table = new DataTable("ProductPatches");
123 + table.Locale = CultureInfo.InvariantCulture;
124 + table.Columns.Add("ProductPatchesPatchCode", typeof(string));
125 +
126 + foreach(PatchInstallation patch in PatchInstallation.GetPatches(null, productCode, null, UserContexts.All, PatchStates.Applied))
127 + {
128 + table.Rows.Add(new object[] { patch.PatchCode });
129 + }
130 + return new DataView(table, "", "ProductPatchesPatchCode ASC", DataViewRowState.CurrentRows);
131 + }
132 +
133 + private DataView GetPatchData(string patchCode)
134 + {
135 + DataTable table = new DataTable("PatchProperties");
136 + table.Locale = CultureInfo.InvariantCulture;
137 + table.Columns.Add("PatchPropertiesProperty", typeof(string));
138 + table.Columns.Add("PatchPropertiesValue", typeof(string));
139 +
140 + table.Rows.Add(new object[] { "PatchCode", patchCode });
141 +
142 + PatchInstallation patch = new PatchInstallation(patchCode, null);
143 +
144 + string localPackage = null;
145 + foreach(string property in new string[]
146 + {
147 + "InstallDate",
148 + "LocalPackage",
149 + "State",
150 + "Transforms",
151 + "Uninstallable",
152 + })
153 + {
154 + try
155 + {
156 + string value = patch[property];
157 + table.Rows.Add(new object[] { property, (value != null ? value : "") });
158 + if(property == "LocalPackage") localPackage = value;
159 + }
160 + catch(InstallerException iex)
161 + {
162 + table.Rows.Add(new object[] { property, iex.Message });
163 + }
164 + catch(ArgumentException) { }
165 + }
166 +
167 + if(localPackage != null)
168 + {
169 + try
170 + {
171 + using(SummaryInfo patchSummaryInfo = new SummaryInfo(localPackage, false))
172 + {
173 + table.Rows.Add(new object[] { "Title", patchSummaryInfo.Title });
174 + table.Rows.Add(new object[] { "Subject", patchSummaryInfo.Subject });
175 + table.Rows.Add(new object[] { "Author", patchSummaryInfo.Author });
176 + table.Rows.Add(new object[] { "Comments", patchSummaryInfo.Comments });
177 + table.Rows.Add(new object[] { "TargetProductCodes", patchSummaryInfo.Template });
178 + string obsoletedPatchCodes = patchSummaryInfo.RevisionNumber.Substring(patchSummaryInfo.RevisionNumber.IndexOf('}') + 1);
179 + table.Rows.Add(new object[] { "ObsoletedPatchCodes", obsoletedPatchCodes });
180 + table.Rows.Add(new object[] { "TransformNames", patchSummaryInfo.LastSavedBy });
181 + }
182 + }
183 + catch(InstallerException) { }
184 + catch(IOException) { }
185 + catch(SecurityException) { }
186 + }
187 + return new DataView(table, "", "PatchPropertiesProperty ASC", DataViewRowState.CurrentRows);
188 + }
189 +
190 + private DataView GetPatchTargetData(string patchCode)
191 + {
192 + DataTable table = new DataTable("PatchTargets");
193 + table.Locale = CultureInfo.InvariantCulture;
194 + table.Columns.Add("PatchTargetsProductName", typeof(string));
195 + table.Columns.Add("PatchTargetsProductCode", typeof(string));
196 +
197 + foreach (PatchInstallation patch in PatchInstallation.GetPatches(patchCode, null, null, UserContexts.All, PatchStates.Applied))
198 + {
199 + if(patch.PatchCode == patchCode)
200 + {
201 + string productName = MsiUtils.GetProductName(patch.ProductCode);
202 + table.Rows.Add(new object[] { productName, patch.ProductCode });
203 + }
204 + }
205 + return new DataView(table, "", "PatchTargetsProductName ASC", DataViewRowState.CurrentRows);
206 + }
207 +
208 + public string GetLink(string nodePath, DataRow row)
209 + {
210 + string[] path = nodePath.Split('\\');
211 +
212 + if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
213 + {
214 + return String.Format(@"Patches\{0}", row["ProductPatchesPatchCode"]);
215 + }
216 + else if(path.Length == 1 && path[0] == "Patches")
217 + {
218 + return String.Format(@"Patches\{0}", row["PatchesPatchCode"]);
219 + }
220 + else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
221 + {
222 + return String.Format(@"Products\{0}", MsiUtils.GetProductCode((string) row["PatchTargetsProductCode"]));
223 + }
224 + return null;
225 + }
226 + }
227 +}
src/samples/Dtf/Inventory/products.cs new
+145
@@ -0,0 +1,145 @@
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 +using System;
4 +using System.Data;
5 +using System.Globalization;
6 +using System.Collections;
7 +using System.Windows.Forms;
8 +using WixToolset.Dtf.WindowsInstaller;
9 +
10 +namespace WixToolset.Dtf.Samples.Inventory
11 +{
12 + /// <summary>
13 + /// Provides inventory data about products installed or advertised on the system.
14 + /// </summary>
15 + public class ProductsInventory : IInventoryDataProvider
16 + {
17 + public ProductsInventory()
18 + {
19 + }
20 +
21 + public string Description
22 + {
23 + get { return "Installed products"; }
24 + }
25 +
26 + public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
27 + {
28 + statusCallback(0, "Products");
29 + ArrayList nodes = new ArrayList();
30 + nodes.Add("Products");
31 + foreach(ProductInstallation product in ProductInstallation.AllProducts)
32 + {
33 + nodes.Add("Products\\" + MsiUtils.GetProductName(product.ProductCode));
34 + }
35 + statusCallback(nodes.Count, String.Empty);
36 + return (string[]) nodes.ToArray(typeof(string));
37 + }
38 +
39 + public bool IsNodeSearchable(string searchRoot, string searchNode)
40 + {
41 + return true;
42 + }
43 +
44 + public DataView GetData(string nodePath)
45 + {
46 + string[] path = nodePath.Split('\\');
47 +
48 + if(path.Length == 1 && path[0] == "Products")
49 + {
50 + return this.GetAllProductsData();
51 + }
52 + else if(path.Length == 2 && path[0] == "Products")
53 + {
54 + return this.GetProductData(MsiUtils.GetProductCode(path[1]));
55 + }
56 + return null;
57 + }
58 +
59 + private DataView GetAllProductsData()
60 + {
61 + DataTable table = new DataTable("Products");
62 + table.Locale = CultureInfo.InvariantCulture;
63 + table.Columns.Add("ProductsProductName", typeof(string));
64 + table.Columns.Add("ProductsProductCode", typeof(string));
65 +
66 + foreach (ProductInstallation product in ProductInstallation.AllProducts)
67 + {
68 + string productName = MsiUtils.GetProductName(product.ProductCode);
69 + table.Rows.Add(new object[] { productName, product.ProductCode });
70 + }
71 + return new DataView(table, "", "ProductsProductName ASC", DataViewRowState.CurrentRows);
72 + }
73 +
74 + private DataView GetProductData(string productCode)
75 + {
76 + DataTable table = new DataTable("ProductProperties");
77 + table.Locale = CultureInfo.InvariantCulture;
78 + table.Columns.Add("ProductPropertiesProperty", typeof(string));
79 + table.Columns.Add("ProductPropertiesValue", typeof(string));
80 +
81 + // Add a fake "ProductCode" install property, just for display convenience.
82 + table.Rows.Add(new object[] { "ProductCode", productCode });
83 +
84 + ProductInstallation product = new ProductInstallation(productCode);
85 +
86 + foreach(string property in new string[]
87 + {
88 + "AssignmentType",
89 + "DiskPrompt",
90 + "HelpLink",
91 + "HelpTelephone",
92 + "InstalledProductName",
93 + "InstallDate",
94 + "InstallLocation",
95 + "InstallSource",
96 + "Language",
97 + "LastUsedSource",
98 + "LastUsedType",
99 + "LocalPackage",
100 + "MediaPackagePath",
101 + "PackageCode",
102 + "PackageName",
103 + "ProductIcon",
104 + "ProductID",
105 + "ProductName",
106 + "Publisher",
107 + "RegCompany",
108 + "RegOwner",
109 + "State",
110 + "transforms",
111 + "Uninstallable",
112 + "UrlInfoAbout",
113 + "UrlUpdateInfo",
114 + "Version",
115 + "VersionMinor",
116 + "VersionMajor",
117 + "VersionString"
118 + })
119 + {
120 + try
121 + {
122 + string value = product[property];
123 + table.Rows.Add(new object[] { property, (value != null ? value : "") });
124 + }
125 + catch(InstallerException iex)
126 + {
127 + table.Rows.Add(new object[] { property, iex.Message });
128 + }
129 + catch(ArgumentException) { }
130 + }
131 + return new DataView(table, "", "ProductPropertiesProperty ASC", DataViewRowState.CurrentRows);
132 + }
133 +
134 + public string GetLink(string nodePath, DataRow row)
135 + {
136 + string[] path = nodePath.Split('\\');
137 +
138 + if(path.Length == 1 && path[0] == "Products")
139 + {
140 + return String.Format(@"Products\{0}", MsiUtils.GetProductName((string) row["ProductsProductCode"]));
141 + }
142 + return null;
143 + }
144 + }
145 +}
src/samples/Dtf/Inventory/xp.manifest new
+15
@@ -0,0 +1,15 @@
1 +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 + <dependency>
7 + <dependentAssembly>
8 + <assemblyIdentity type="win32"
9 + name="Microsoft.Windows.Common-Controls"
10 + version="6.0.0.0" language="*"
11 + processorArchitecture="X86"
12 + publicKeyToken="6595b64144ccf1df" />
13 + </dependentAssembly>
14 + </dependency>
15 +</assembly>
src/samples/Dtf/ManagedCA/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Reflection;
4 +
5 +[assembly: AssemblyDescription("Sample managed custom actions")]
src/samples/Dtf/ManagedCA/ManagedCA.csproj new
+33
@@ -0,0 +1,33 @@
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 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 + <PropertyGroup>
4 + <ProjectGuid>{DB9E5F02-8241-440A-9B60-980EB5B42B13}</ProjectGuid>
5 + <OutputType>Library</OutputType>
6 + <RootNamespace>WixToolset.Dtf.Samples.ManagedCA</RootNamespace>
7 + <AssemblyName>WixToolset.Dtf.Samples.ManagedCA</AssemblyName>
8 + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
9 + <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
10 + </PropertyGroup>
11 + <ItemGroup>
12 + <Compile Include="AssemblyInfo.cs" />
13 + <Compile Include="SampleCAs.cs" />
14 + </ItemGroup>
15 + <ItemGroup>
16 + <Reference Include="System" />
17 + </ItemGroup>
18 + <ItemGroup>
19 + <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
20 + <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
21 + <Name>WixToolset.Dtf.WindowsInstaller</Name>
22 + </ProjectReference>
23 + </ItemGroup>
24 +
25 + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
26 +
27 +<!--
28 + <PropertyGroup>
29 + <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" WixToolset.Dtf.WindowsInstaller.dll="$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll" testsub\SampleCAs.cs="$(ProjectDir)\SampleCAs.cs"</PostBuildEvent>
30 + </PropertyGroup>
31 +-->
32 +
33 +</Project>
src/samples/Dtf/ManagedCA/SampleCAs.cs new
+127
@@ -0,0 +1,127 @@
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 +namespace WixToolset.Dtf.Samples.ManagedCA
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.IO;
8 + using WixToolset.Dtf.WindowsInstaller;
9 +
10 + public class SampleCAs
11 + {
12 + [CustomAction]
13 + public static ActionResult SampleCA1(Session session)
14 + {
15 + using (Record msgRec = new Record(0))
16 + {
17 + msgRec[0] = "Hello from SampleCA1!" +
18 + "\r\nCLR version is v" + Environment.Version;
19 + session.Message(InstallMessage.Info, msgRec);
20 + session.Message(InstallMessage.User, msgRec);
21 + }
22 +
23 + session.Log("Testing summary info...");
24 + SummaryInfo summInfo = session.Database.SummaryInfo;
25 + session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber);
26 + session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime);
27 +
28 + string testProp = session["SampleCATest"];
29 + session.Log("Simple property test: [SampleCATest]={0}.", testProp);
30 +
31 + session.Log("Testing subdirectory extraction...");
32 + string testFilePath = "testsub\\SampleCAs.cs";
33 + if (!File.Exists(testFilePath))
34 + {
35 + session.Log("Subdirectory extraction failed. File not found: " + testFilePath);
36 + return ActionResult.Failure;
37 + }
38 + else
39 + {
40 + session.Log("Found file extracted in subdirectory.");
41 + }
42 +
43 + session.Log("Testing record stream extraction...");
44 + string tempFile = null;
45 + try
46 + {
47 + tempFile = Path.GetTempFileName();
48 + using (View binView = session.Database.OpenView(
49 + "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " +
50 + "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " +
51 + "`CustomAction`.`Source` = `Binary`.`Name`"))
52 + {
53 + binView.Execute();
54 + using (Record binRec = binView.Fetch())
55 + {
56 + binRec.GetStream(1, tempFile);
57 + }
58 + }
59 +
60 + session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length);
61 + string binFileVersion = Installer.GetFileVersion(tempFile);
62 + session.Log("CA binary file version: {0}", binFileVersion);
63 + }
64 + finally
65 + {
66 + if (tempFile != null && File.Exists(tempFile))
67 + {
68 + File.Delete(tempFile);
69 + }
70 + }
71 +
72 + session.Log("Testing record stream reading...");
73 + using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'"))
74 + {
75 + binView2.Execute();
76 + using (Record binRec2 = binView2.Fetch())
77 + {
78 + Stream stream = binRec2.GetStream("Data");
79 + string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd();
80 + session.Log("Test data: " + testData);
81 + }
82 + }
83 +
84 + session.Log("Listing components");
85 + using (View compView = session.Database.OpenView(
86 + "SELECT `Component` FROM `Component`"))
87 + {
88 + compView.Execute();
89 + foreach (Record compRec in compView)
90 + {
91 + using (compRec)
92 + {
93 + session.Log("\t{0}", compRec["Component"]);
94 + }
95 + }
96 + }
97 +
98 + session.Log("Testing the ability to access an external MSI database...");
99 + string tempDbFile = Path.GetTempFileName();
100 + using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect))
101 + {
102 + // Just create an empty database.
103 + }
104 + using (Database tempDb2 = new Database(tempDbFile))
105 + {
106 + // See if we can open and query the database.
107 + IList<string> tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`");
108 + session.Log("Found " + tables.Count + " tables in the newly created database.");
109 + }
110 + File.Delete(tempDbFile);
111 +
112 + return ActionResult.Success;
113 + }
114 +
115 + [CustomAction("SampleCA2")]
116 + public static ActionResult SampleCustomAction2(Session session)
117 + {
118 + using (Record msgRec = new Record(0))
119 + {
120 + msgRec[0] = "Hello from SampleCA2!";
121 + session.Message(InstallMessage.Info, msgRec);
122 + session.Message(InstallMessage.User, msgRec);
123 + }
124 + return ActionResult.UserExit;
125 + }
126 + }
127 +}
src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs new
+711
@@ -0,0 +1,711 @@
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 +namespace WixToolset.Dtf.Tools.MakeSfxCA
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 + using System.Security;
9 + using System.Text;
10 + using System.Reflection;
11 + using Compression;
12 + using Compression.Cab;
13 + using Resources;
14 + using ResourceCollection = Resources.ResourceCollection;
15 +
16 + /// <summary>
17 + /// Command-line tool for building self-extracting custom action packages.
18 + /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's
19 + /// entry-points and file version to look like the CA module.
20 + /// </summary>
21 + public static class MakeSfxCA
22 + {
23 + private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll";
24 +
25 + private static TextWriter log;
26 +
27 + /// <summary>
28 + /// Prints usage text for the tool.
29 + /// </summary>
30 + /// <param name="w">Console text writer.</param>
31 + public static void Usage(TextWriter w)
32 + {
33 + w.WriteLine("Deployment Tools Foundation custom action packager version {0}",
34 + Assembly.GetExecutingAssembly().GetName().Version);
35 + w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved.");
36 + w.WriteLine();
37 + w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]");
38 + w.WriteLine();
39 + w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package.");
40 + w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY);
41 + w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config");
42 + }
43 +
44 + /// <summary>
45 + /// Runs the MakeSfxCA command-line tool.
46 + /// </summary>
47 + /// <param name="args">Command-line arguments.</param>
48 + /// <returns>0 on success, nonzero on failure.</returns>
49 + public static int Main(string[] args)
50 + {
51 + if (args.Length < 3)
52 + {
53 + Usage(Console.Out);
54 + return 1;
55 + }
56 +
57 + var output = args[0];
58 + var sfxDll = args[1];
59 + var inputs = new string[args.Length - 2];
60 + Array.Copy(args, 2, inputs, 0, inputs.Length);
61 +
62 + try
63 + {
64 + Build(output, sfxDll, inputs, Console.Out);
65 + return 0;
66 + }
67 + catch (ArgumentException ex)
68 + {
69 + Console.Error.WriteLine("Error: Invalid argument: " + ex.Message);
70 + return 1;
71 + }
72 + catch (FileNotFoundException ex)
73 + {
74 + Console.Error.WriteLine("Error: Cannot find file: " + ex.Message);
75 + return 1;
76 + }
77 + catch (Exception ex)
78 + {
79 + Console.Error.WriteLine("Error: Unexpected error: " + ex);
80 + return 1;
81 + }
82 + }
83 +
84 + /// <summary>
85 + /// Packages up all the inputs to the output location.
86 + /// </summary>
87 + /// <exception cref="Exception">Various exceptions are thrown
88 + /// if things go wrong.</exception>
89 + public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log)
90 + {
91 + MakeSfxCA.log = log;
92 +
93 + if (string.IsNullOrEmpty(output))
94 + {
95 + throw new ArgumentNullException("output");
96 + }
97 +
98 + if (string.IsNullOrEmpty(sfxDll))
99 + {
100 + throw new ArgumentNullException("sfxDll");
101 + }
102 +
103 + if (inputs == null || inputs.Count == 0)
104 + {
105 + throw new ArgumentNullException("inputs");
106 + }
107 +
108 + if (!File.Exists(sfxDll))
109 + {
110 + throw new FileNotFoundException(sfxDll);
111 + }
112 +
113 + var customActionAssembly = inputs[0];
114 + if (!File.Exists(customActionAssembly))
115 + {
116 + throw new FileNotFoundException(customActionAssembly);
117 + }
118 +
119 + inputs = MakeSfxCA.SplitList(inputs);
120 +
121 + var inputsMap = MakeSfxCA.GetPackFileMap(inputs);
122 +
123 + var foundWIAssembly = false;
124 + foreach (var input in inputsMap.Keys)
125 + {
126 + if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY,
127 + StringComparison.OrdinalIgnoreCase) == 0)
128 + {
129 + foundWIAssembly = true;
130 + }
131 + }
132 +
133 + if (!foundWIAssembly)
134 + {
135 + throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY +
136 + " must be included in the list of support files. " +
137 + "If using the MSBuild targets, make sure the assembly reference " +
138 + "has the Private (Copy Local) flag set.");
139 + }
140 +
141 + MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly));
142 +
143 + var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly);
144 + var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly);
145 +
146 + if (entryPoints.Count == 0 && uiClass == null)
147 + {
148 + throw new ArgumentException(
149 + "No CA or UI entry points found in module: " + customActionAssembly);
150 + }
151 + else if (entryPoints.Count > 0 && uiClass != null)
152 + {
153 + throw new NotSupportedException(
154 + "CA and UI entry points cannot be in the same assembly: " + customActionAssembly);
155 + }
156 +
157 + var dir = Path.GetDirectoryName(output);
158 + if (dir.Length > 0 && !Directory.Exists(dir))
159 + {
160 + Directory.CreateDirectory(dir);
161 + }
162 +
163 + using (Stream outputStream = File.Create(output))
164 + {
165 + MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass);
166 + }
167 +
168 + MakeSfxCA.CopyVersionResource(customActionAssembly, output);
169 +
170 + MakeSfxCA.PackInputFiles(output, inputsMap);
171 +
172 + log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName);
173 + }
174 +
175 + /// <summary>
176 + /// Splits any list items delimited by semicolons into separate items.
177 + /// </summary>
178 + /// <param name="list">Read-only input list.</param>
179 + /// <returns>New list with resulting split items.</returns>
180 + private static IList<string> SplitList(IList<string> list)
181 + {
182 + var newList = new List<string>(list.Count);
183 +
184 + foreach (var item in list)
185 + {
186 + if (!string.IsNullOrEmpty(item))
187 + {
188 + foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
189 + {
190 + newList.Add(splitItem);
191 + }
192 + }
193 + }
194 +
195 + return newList;
196 + }
197 +
198 + /// <summary>
199 + /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection.
200 + /// </summary>
201 + /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param>
202 + /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param>
203 + /// <remarks>
204 + /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them
205 + /// to the list of input files if found.
206 + /// </remarks>
207 + private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir)
208 + {
209 + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args)
210 + {
211 + AssemblyName resolveName = new AssemblyName(args.Name);
212 + Assembly assembly = null;
213 +
214 + // First, try to find the assembly in the list of input files.
215 + foreach (var inputFile in inputFiles.Values)
216 + {
217 + var inputName = Path.GetFileNameWithoutExtension(inputFile);
218 + var inputExtension = Path.GetExtension(inputFile);
219 + if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) &&
220 + (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) ||
221 + string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase)))
222 + {
223 + assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile);
224 +
225 + if (assembly != null)
226 + {
227 + break;
228 + }
229 + }
230 + }
231 +
232 + // Second, try to find the assembly in the input directory.
233 + if (assembly == null && inputDir != null)
234 + {
235 + string assemblyPath = null;
236 + if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll"))
237 + {
238 + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll";
239 + }
240 + else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe"))
241 + {
242 + assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe";
243 + }
244 +
245 + if (assemblyPath != null)
246 + {
247 + assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath);
248 +
249 + if (assembly != null)
250 + {
251 + // Add this detected dependency to the list of files to be packed.
252 + inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath);
253 + }
254 + }
255 + }
256 +
257 + // Third, try to load the assembly from the GAC.
258 + if (assembly == null)
259 + {
260 + try
261 + {
262 + assembly = Assembly.ReflectionOnlyLoad(args.Name);
263 + }
264 + catch (FileNotFoundException)
265 + {
266 + }
267 + }
268 +
269 + if (assembly != null)
270 + {
271 + if (string.Equals(assembly.GetName().ToString(), resolveName.ToString()))
272 + {
273 + log.WriteLine(" Loaded dependent assembly: " + assembly.Location);
274 + return assembly;
275 + }
276 +
277 + log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location);
278 + log.WriteLine(" Loaded assembly : " + assembly.GetName());
279 + log.WriteLine(" Reference assembly: " + resolveName);
280 + }
281 + else
282 + {
283 + log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName);
284 + }
285 +
286 + return null;
287 + };
288 + }
289 +
290 + /// <summary>
291 + /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails.
292 + /// </summary>
293 + /// <param name="assemblyPath">Path of the assembly file to laod.</param>
294 + /// <returns>Loaded assembly, or null if the load failed.</returns>
295 + private static Assembly TryLoadDependentAssembly(string assemblyPath)
296 + {
297 + Assembly assembly = null;
298 + try
299 + {
300 + assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
301 + }
302 + catch (IOException ex)
303 + {
304 + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
305 + }
306 + catch (BadImageFormatException ex)
307 + {
308 + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
309 + }
310 + catch (SecurityException ex)
311 + {
312 + log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
313 + }
314 +
315 + return assembly;
316 + }
317 +
318 + /// <summary>
319 + /// Searches the types in the input assembly for a type that implements IEmbeddedUI.
320 + /// </summary>
321 + /// <param name="module"></param>
322 + /// <returns></returns>
323 + private static string FindEmbeddedUIClass(string module)
324 + {
325 + log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module));
326 +
327 + string uiClass = null;
328 +
329 + var assembly = Assembly.ReflectionOnlyLoadFrom(module);
330 +
331 + foreach (var type in assembly.GetExportedTypes())
332 + {
333 + if (!type.IsAbstract)
334 + {
335 + foreach (var interfaceType in type.GetInterfaces())
336 + {
337 + if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI")
338 + {
339 + if (uiClass == null)
340 + {
341 + uiClass = assembly.GetName().Name + "!" + type.FullName;
342 + }
343 + else
344 + {
345 + throw new ArgumentException("Multiple IEmbeddedUI implementations found.");
346 + }
347 + }
348 + }
349 + }
350 + }
351 +
352 + return uiClass;
353 + }
354 +
355 + /// <summary>
356 + /// Reflects on an input CA module to locate custom action entry-points.
357 + /// </summary>
358 + /// <param name="module">Assembly module with CA entry-points.</param>
359 + /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns>
360 + private static IDictionary<string, string> FindEntryPoints(string module)
361 + {
362 + log.WriteLine("Searching for custom action entry points " +
363 + "in {0}", Path.GetFileName(module));
364 +
365 + var entryPoints = new Dictionary<string, string>();
366 +
367 + var assembly = Assembly.ReflectionOnlyLoadFrom(module);
368 +
369 + foreach (var type in assembly.GetExportedTypes())
370 + {
371 + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
372 + {
373 + var entryPointName = MakeSfxCA.GetEntryPoint(method);
374 + if (entryPointName != null)
375 + {
376 + var entryPointPath = string.Format(
377 + "{0}!{1}.{2}",
378 + Path.GetFileNameWithoutExtension(module),
379 + type.FullName,
380 + method.Name);
381 + entryPoints.Add(entryPointName, entryPointPath);
382 +
383 + log.WriteLine(" {0}={1}", entryPointName, entryPointPath);
384 + }
385 + }
386 + }
387 +
388 + return entryPoints;
389 + }
390 +
391 + /// <summary>
392 + /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method.
393 + /// </summary>
394 + /// <param name="method">A public static method.</param>
395 + /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name,
396 + /// or null if the method is not a custom action method.</returns>
397 + private static string GetEntryPoint(MethodInfo method)
398 + {
399 + IList<CustomAttributeData> attributes;
400 + try
401 + {
402 + attributes = CustomAttributeData.GetCustomAttributes(method);
403 + }
404 + catch (FileLoadException)
405 + {
406 + // Already logged load failures in the assembly-resolve-handler.
407 + return null;
408 + }
409 +
410 + foreach (CustomAttributeData attribute in attributes)
411 + {
412 + if (attribute.ToString().StartsWith(
413 + "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(",
414 + StringComparison.Ordinal))
415 + {
416 + string entryPointName = null;
417 + foreach (var argument in attribute.ConstructorArguments)
418 + {
419 + // The entry point name is the first positional argument, if specified.
420 + entryPointName = (string) argument.Value;
421 + break;
422 + }
423 +
424 + if (string.IsNullOrEmpty(entryPointName))
425 + {
426 + entryPointName = method.Name;
427 + }
428 +
429 + return entryPointName;
430 + }
431 + }
432 +
433 + return null;
434 + }
435 +
436 + /// <summary>
437 + /// Counts the number of template entrypoints in SfxCA.dll.
438 + /// </summary>
439 + /// <remarks>
440 + /// Depending on the requirements, SfxCA.dll might be built with
441 + /// more entrypoints than the default.
442 + /// </remarks>
443 + private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat)
444 + {
445 + for (var count = 0; ; count++)
446 + {
447 + var templateName = string.Format(entryPointFormat, count);
448 + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
449 +
450 + var nameOffset = FindBytes(fileBytes, templateAsciiBytes);
451 + if (nameOffset < 0)
452 + {
453 + return count;
454 + }
455 + }
456 + }
457 +
458 + /// <summary>
459 + /// Writes a modified version of SfxCA.dll to the output stream,
460 + /// with the template entry-points mapped to the CA entry-points.
461 + /// </summary>
462 + /// <remarks>
463 + /// To avoid having to recompile SfxCA.dll for every different set of CAs,
464 + /// this method looks for a preset number of template entry-points in the
465 + /// binary file and overwrites their entrypoint name and string data with
466 + /// CA-specific values.
467 + /// </remarks>
468 + private static void WriteEntryModule(
469 + string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass)
470 + {
471 + log.WriteLine("Modifying SfxCA.dll stub");
472 +
473 + byte[] fileBytes;
474 + using (var readStream = File.OpenRead(sfxDll))
475 + {
476 + fileBytes = new byte[(int) readStream.Length];
477 + readStream.Read(fileBytes, 0, fileBytes.Length);
478 + }
479 +
480 + const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}";
481 + const int MAX_ENTRYPOINT_NAME = 72;
482 + const int MAX_ENTRYPOINT_PATH = 160;
483 + //var emptyBytes = new byte[0];
484 +
485 + var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT);
486 +
487 + if (slotCount == 0)
488 + {
489 + throw new ArgumentException("Invalid SfxCA.dll file.");
490 + }
491 +
492 + if (entryPoints.Count > slotCount)
493 + {
494 + throw new ArgumentException(string.Format(
495 + "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " +
496 + "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.",
497 + entryPoints.Count, slotCount));
498 + }
499 +
500 + var slotSort = new string[slotCount];
501 + for (var i = 0; i < slotCount - entryPoints.Count; i++)
502 + {
503 + slotSort[i] = string.Empty;
504 + }
505 +
506 + entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count);
507 + Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal);
508 +
509 + for (var i = 0; ; i++)
510 + {
511 + var templateName = string.Format(ENTRYPOINT_FORMAT, i);
512 + var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
513 + var templateUniBytes = Encoding.Unicode.GetBytes(templateName);
514 +
515 + var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes);
516 + if (nameOffset < 0)
517 + {
518 + break;
519 + }
520 +
521 + var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes);
522 + if (pathOffset < 0)
523 + {
524 + break;
525 + }
526 +
527 + var entryPointName = slotSort[i];
528 + var entryPointPath = entryPointName.Length > 0 ?
529 + entryPoints[entryPointName] : string.Empty;
530 +
531 + if (entryPointName.Length > MAX_ENTRYPOINT_NAME)
532 + {
533 + throw new ArgumentException(string.Format(
534 + "Entry point name exceeds limit of {0} characters: {1}",
535 + MAX_ENTRYPOINT_NAME,
536 + entryPointName));
537 + }
538 +
539 + if (entryPointPath.Length > MAX_ENTRYPOINT_PATH)
540 + {
541 + throw new ArgumentException(string.Format(
542 + "Entry point path exceeds limit of {0} characters: {1}",
543 + MAX_ENTRYPOINT_PATH,
544 + entryPointPath));
545 + }
546 +
547 + var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName);
548 + var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath);
549 +
550 + MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes);
551 + MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes);
552 + }
553 +
554 + if (entryPoints.Count == 0 && uiClass != null)
555 + {
556 + // Remove the zzz prefix from exported EmbeddedUI entry-points.
557 + foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" })
558 + {
559 + var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export);
560 +
561 + var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes);
562 + if (exportOffset < 0)
563 + {
564 + throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export);
565 + }
566 +
567 + var replaceNameBytes = Encoding.ASCII.GetBytes(export);
568 + MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes);
569 + }
570 +
571 + if (uiClass.Length > MAX_ENTRYPOINT_PATH)
572 + {
573 + throw new ArgumentException(string.Format(
574 + "UI class full name exceeds limit of {0} characters: {1}",
575 + MAX_ENTRYPOINT_PATH,
576 + uiClass));
577 + }
578 +
579 + var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName");
580 + var replaceBytes = Encoding.Unicode.GetBytes(uiClass);
581 +
582 + // Fill in the embedded UI implementor class so the proxy knows which one to load.
583 + var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes);
584 + if (replaceOffset >= 0)
585 + {
586 + MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes);
587 + }
588 + }
589 +
590 + outputStream.Write(fileBytes, 0, fileBytes.Length);
591 + }
592 +
593 + /// <summary>
594 + /// Searches for a sub-array of bytes within a larger array of bytes.
595 + /// </summary>
596 + private static int FindBytes(byte[] source, byte[] find)
597 + {
598 + for (var i = 0; i < source.Length; i++)
599 + {
600 + int j;
601 + for (j = 0; j < find.Length; j++)
602 + {
603 + if (source[i + j] != find[j])
604 + {
605 + break;
606 + }
607 + }
608 +
609 + if (j == find.Length)
610 + {
611 + return i;
612 + }
613 + }
614 +
615 + return -1;
616 + }
617 +
618 + /// <summary>
619 + /// Replaces a range of bytes with new bytes, padding any extra part
620 + /// of the range with zeroes.
621 + /// </summary>
622 + private static void ReplaceBytes(
623 + byte[] source, int offset, int length, byte[] replace)
624 + {
625 + for (var i = 0; i < length; i++)
626 + {
627 + if (i < replace.Length)
628 + {
629 + source[offset + i] = replace[i];
630 + }
631 + else
632 + {
633 + source[offset + i] = 0;
634 + }
635 + }
636 + }
637 +
638 + /// <summary>
639 + /// Print the name of one file as it is being packed into the cab.
640 + /// </summary>
641 + private static void PackProgress(object source, ArchiveProgressEventArgs e)
642 + {
643 + if (e.ProgressType == ArchiveProgressType.StartFile && log != null)
644 + {
645 + log.WriteLine(" {0}", e.CurrentFileName);
646 + }
647 + }
648 +
649 + /// <summary>
650 + /// Gets a mapping from filenames as they will be in the cab to filenames
651 + /// as they are currently on disk.
652 + /// </summary>
653 + /// <remarks>
654 + /// By default, all files will be placed in the root of the cab. But inputs may
655 + /// optionally include an alternate inside-cab file path before an equals sign.
656 + /// </remarks>
657 + private static IDictionary<string, string> GetPackFileMap(IList<string> inputs)
658 + {
659 + var fileMap = new Dictionary<string, string>();
660 + foreach (var inputFile in inputs)
661 + {
662 + if (inputFile.IndexOf('=') > 0)
663 + {
664 + var parse = inputFile.Split('=');
665 + if (!fileMap.ContainsKey(parse[0]))
666 + {
667 + fileMap.Add(parse[0], parse[1]);
668 + }
669 + }
670 + else
671 + {
672 + var fileName = Path.GetFileName(inputFile);
673 + if (!fileMap.ContainsKey(fileName))
674 + {
675 + fileMap.Add(fileName, inputFile);
676 + }
677 + }
678 + }
679 + return fileMap;
680 + }
681 +
682 + /// <summary>
683 + /// Packs the input files into a cab that is appended to the
684 + /// output SfxCA.dll.
685 + /// </summary>
686 + private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap)
687 + {
688 + log.WriteLine("Packaging files");
689 +
690 + var cabInfo = new CabInfo(outputFile);
691 + cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress);
692 + }
693 +
694 + /// <summary>
695 + /// Copies the version resource information from the CA module to
696 + /// the CA package. This gives the package the file version and
697 + /// description of the CA module, instead of the version and
698 + /// description of SfxCA.dll.
699 + /// </summary>
700 + private static void CopyVersionResource(string sourceFile, string destFile)
701 + {
702 + log.WriteLine("Copying file version info from {0} to {1}",
703 + sourceFile, destFile);
704 +
705 + var rc = new ResourceCollection();
706 + rc.Find(sourceFile, ResourceType.Version);
707 + rc.Load(sourceFile);
708 + rc.Save(destFile);
709 + }
710 + }
711 +}
src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj new
+33
@@ -0,0 +1,33 @@
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 + <TargetFrameworks>netcoreapp3.1;net461</TargetFrameworks>
7 + <OutputType>Exe</OutputType>
8 + <RootNamespace>WixToolset.Dtf.Tools.MakeSfxCA</RootNamespace>
9 + <AssemblyName>MakeSfxCA</AssemblyName>
10 + <DebugType>embedded</DebugType>
11 + <AppConfig>app.config</AppConfig>
12 + <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest>
13 + <RollForward>Major</RollForward>
14 + <RuntimeIdentifier>win-x86</RuntimeIdentifier>
15 + </PropertyGroup>
16 +
17 + <ItemGroup>
18 + <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
19 + </ItemGroup>
20 +
21 + <ItemGroup>
22 + <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
23 + <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
24 + <ProjectReference Include="..\..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" />
25 + </ItemGroup>
26 +
27 + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
28 + <PropertyGroup>
29 + <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
30 + </PropertyGroup>
31 + <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
32 + </Target>
33 +</Project>
src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest new
+20
@@ -0,0 +1,20 @@
1 +<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 + <assemblyIdentity name="WixToolset.Dtf.Tools.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
7 + <description>WiX Toolset Compiler</description>
8 + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
9 + <security>
10 + <requestedPrivileges>
11 + <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
12 + </requestedPrivileges>
13 + </security>
14 + </trustInfo>
15 + <application xmlns="urn:schemas-microsoft-com:asm.v3">
16 + <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
17 + <ws2:longPathAware>true</ws2:longPathAware>
18 + </windowsSettings>
19 + </application>
20 +</assembly>
src/samples/Dtf/Tools/MakeSfxCA/app.config new
+10
@@ -0,0 +1,10 @@
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 +
5 +<configuration>
6 + <runtime>
7 + <loadFromRemoteSources enabled="true"/>
8 + <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
9 + </runtime>
10 +</configuration>
src/samples/Dtf/Tools/SfxCA/ClrHost.cpp new
+262
@@ -0,0 +1,262 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +
5 +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6 +
7 +//---------------------------------------------------------------------
8 +// CLR HOSTING
9 +//---------------------------------------------------------------------
10 +
11 +/// <summary>
12 +/// Binds to the CLR after determining the appropriate version.
13 +/// </summary>
14 +/// <param name="hSession">Handle to the installer session,
15 +/// used just for logging.</param>
16 +/// <param name="version">Specific version of the CLR to load.
17 +/// If null, then the config file and/or primary assembly are
18 +/// used to determine the version.</param>
19 +/// <param name="szConfigFile">XML .config file which may contain
20 +/// a startup section to direct which version of the CLR to use.
21 +/// May be NULL.</param>
22 +/// <param name="szPrimaryAssembly">Assembly to be used to determine
23 +/// the version of the CLR in the absence of other configuration.
24 +/// May be NULL.</param>
25 +/// <param name="ppHost">Returned runtime host interface.</param>
26 +/// <returns>True if the CLR was loaded successfully, false if
27 +/// there was some error.</returns>
28 +/// <remarks>
29 +/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or
30 +/// does not contain any version configuration, the CLR will not be loaded.
31 +/// </remarks>
32 +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
33 + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost)
34 +{
35 + typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion,
36 + LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags,
37 + LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength,
38 + LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength);
39 + typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor,
40 + DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
41 +
42 + HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll");
43 + if (hmodMscoree == NULL)
44 + {
45 + Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action "
46 + L"requires the .NET Framework to be installed.", GetLastError());
47 + return false;
48 + }
49 + PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo)
50 + GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo");
51 + PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx)
52 + GetProcAddress(hmodMscoree, "CorBindToRuntimeEx");
53 + if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL)
54 + {
55 + Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action "
56 + L"requires the .NET Framework to be installed.", GetLastError());
57 + FreeLibrary(hmodMscoree);
58 + return false;
59 + }
60 +
61 + wchar_t szClrVersion[20];
62 + HRESULT hr;
63 +
64 + if (szVersion != NULL && szVersion[0] != L'\0')
65 + {
66 + wcsncpy_s(szClrVersion, 20, szVersion, 20);
67 + }
68 + else
69 + {
70 + wchar_t szVersionDir[MAX_PATH];
71 + hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL,
72 + szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL);
73 + if (FAILED(hr))
74 + {
75 + Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr);
76 + Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or "
77 + L"that there is a matching supportedRuntime element in CustomAction.config. "
78 + L"If you are binding to .NET 4 or greater add "
79 + L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element.");
80 + FreeLibrary(hmodMscoree);
81 + return false;
82 + }
83 + }
84 +
85 + Log(hSession, L"Binding to CLR version %s", szClrVersion);
86 +
87 + ICorRuntimeHost* pHost;
88 + hr = pCorBindToRuntimeEx(szClrVersion, NULL,
89 + STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN,
90 + CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost);
91 + if (FAILED(hr))
92 + {
93 + Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr);
94 + FreeLibrary(hmodMscoree);
95 + return false;
96 + }
97 + hr = pHost->Start();
98 + if (FAILED(hr))
99 + {
100 + Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr);
101 + pHost->Release();
102 + FreeLibrary(hmodMscoree);
103 + return false;
104 + }
105 + *ppHost = pHost;
106 + FreeLibrary(hmodMscoree);
107 + return true;
108 +}
109 +
110 +/// <summary>
111 +/// Creates a new CLR application domain.
112 +/// </summary>
113 +/// <param name="hSession">Handle to the installer session,
114 +/// used just for logging</param>
115 +/// <param name="pHost">Interface to the runtime host where the
116 +/// app domain will be created.</param>
117 +/// <param name="szName">Name of the app domain to create.</param>
118 +/// <param name="szAppBase">Application base directory path, where
119 +/// the app domain will look first to load its assemblies.</param>
120 +/// <param name="szConfigFile">Optional XML .config file containing any
121 +/// configuration for thae app domain.</param>
122 +/// <param name="ppAppDomain">Returned app domain interface.</param>
123 +/// <returns>True if the app domain was created successfully, false if
124 +/// there was some error.</returns>
125 +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
126 + const wchar_t* szName, const wchar_t* szAppBase,
127 + const wchar_t* szConfigFile, _AppDomain** ppAppDomain)
128 +{
129 + IUnknown* punkAppDomainSetup = NULL;
130 + IAppDomainSetup* pAppDomainSetup = NULL;
131 + HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup);
132 + if (SUCCEEDED(hr))
133 + {
134 + hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup);
135 + punkAppDomainSetup->Release();
136 + }
137 + if (FAILED(hr))
138 + {
139 + Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr);
140 + return false;
141 + }
142 +
143 + const wchar_t* szUrlPrefix = L"file:///";
144 + size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase);
145 + wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t));
146 + if (szApplicationBase == NULL) hr = E_OUTOFMEMORY;
147 + else
148 + {
149 + StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix);
150 + StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase);
151 + BSTR bstrApplicationBase = SysAllocString(szApplicationBase);
152 + if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY;
153 + else
154 + {
155 + hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase);
156 + SysFreeString(bstrApplicationBase);
157 + }
158 + }
159 +
160 + if (SUCCEEDED(hr) && szConfigFile != NULL)
161 + {
162 + BSTR bstrConfigFile = SysAllocString(szConfigFile);
163 + if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY;
164 + else
165 + {
166 + hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile);
167 + SysFreeString(bstrConfigFile);
168 + }
169 + }
170 +
171 + if (FAILED(hr))
172 + {
173 + Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr);
174 + pAppDomainSetup->Release();
175 + return false;
176 + }
177 +
178 + IUnknown* punkAppDomain;
179 + hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain);
180 + pAppDomainSetup->Release();
181 + if (SUCCEEDED(hr))
182 + {
183 + hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain);
184 + punkAppDomain->Release();
185 + }
186 +
187 + if (FAILED(hr))
188 + {
189 + Log(hSession, L"Failed to create app domain. Error code 0x%X", hr);
190 + return false;
191 + }
192 +
193 + return true;
194 +}
195 +
196 +/// <summary>
197 +/// Locates a specific method in a specific class and assembly.
198 +/// </summary>
199 +/// <param name="hSession">Handle to the installer session,
200 +/// used just for logging</param>
201 +/// <param name="pAppDomain">Application domain in which to
202 +/// load assemblies.</param>
203 +/// <param name="szAssembly">Display name of the assembly
204 +/// containing the method.</param>
205 +/// <param name="szClass">Fully-qualified name of the class
206 +/// containing the method.</param>
207 +/// <param name="szMethod">Name of the method.</param>
208 +/// <param name="ppMethod">Returned method interface.</param>
209 +/// <returns>True if the method was located, otherwise false.</returns>
210 +/// <remarks>Only public static methods are searched. Method
211 +/// parameter types are not considered; if there are multiple
212 +/// matching methods with different parameters, an error results.</remarks>
213 +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
214 + const wchar_t* szAssembly, const wchar_t* szClass,
215 + const wchar_t* szMethod, _MethodInfo** ppMethod)
216 +{
217 + HRESULT hr;
218 + _Assembly* pAssembly = NULL;
219 + BSTR bstrAssemblyName = SysAllocString(szAssembly);
220 + if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY;
221 + else
222 + {
223 + hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
224 + SysFreeString(bstrAssemblyName);
225 + }
226 + if (FAILED(hr))
227 + {
228 + Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr);
229 + return false;
230 + }
231 +
232 + _Type* pType = NULL;
233 + BSTR bstrClass = SysAllocString(szClass);
234 + if (bstrClass == NULL) hr = E_OUTOFMEMORY;
235 + else
236 + {
237 + hr = pAssembly->GetType_2(bstrClass, &pType);
238 + SysFreeString(bstrClass);
239 + }
240 + pAssembly->Release();
241 + if (FAILED(hr) || pType == NULL)
242 + {
243 + Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr);
244 + return false;
245 + }
246 +
247 + BSTR bstrMethod = SysAllocString(szMethod);
248 + if (bstrMethod == NULL) hr = E_OUTOFMEMORY;
249 + else
250 + {
251 + hr = pType->GetMethod_2(bstrMethod,
252 + (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod);
253 + SysFreeString(bstrMethod);
254 + }
255 + pType->Release();
256 + if (FAILED(hr) || *ppMethod == NULL)
257 + {
258 + Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr);
259 + return false;
260 + }
261 + return true;
262 +}
src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp new
+281
@@ -0,0 +1,281 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +#include "SfxUtil.h"
5 +
6 +// Globals for keeping track of things across UI messages.
7 +static const wchar_t* g_szWorkingDir;
8 +static ICorRuntimeHost* g_pClrHost;
9 +static _AppDomain* g_pAppDomain;
10 +static _MethodInfo* g_pProcessMessageMethod;
11 +static _MethodInfo* g_pShutdownMethod;
12 +
13 +// Reserve extra space for strings to be replaced at build time.
14 +#define NULLSPACE \
15 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
16 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
17 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
18 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
19 +
20 +// Prototypes for local functions.
21 +// See the function definitions for comments.
22 +
23 +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession,
24 + const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult);
25 +
26 +/// <summary>
27 +/// First entry-point for the UI DLL when loaded and called by MSI.
28 +/// Extracts the payload, hosts the CLR, and invokes the managed
29 +/// initialize method.
30 +/// </summary>
31 +/// <param name="hSession">Handle to the installer session,
32 +/// used for logging errors and to be passed on to the managed initialize method.</param>
33 +/// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table
34 +/// have been extracted, and where additional payload from this package will be extracted.</param>
35 +/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
36 +/// the managed initialize method.</param>
37 +extern "C"
38 +UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel)
39 +{
40 + // If the managed initialize method cannot be called, continue the installation in BASIC UI mode.
41 + UINT uiResult = INSTALLUILEVEL_BASIC;
42 +
43 + const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE;
44 +
45 + g_szWorkingDir = szResourcePath;
46 +
47 + wchar_t szModule[MAX_PATH];
48 + DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1);
49 + if (cchCopied == 0)
50 + {
51 + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
52 + return uiResult;
53 + }
54 + else if (cchCopied == MAX_PATH - 1)
55 + {
56 + Log(hSession, L"Failed to get module path -- path is too long.");
57 + return uiResult;
58 + }
59 +
60 + Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir);
61 + int err = ExtractCabinet(szModule, g_szWorkingDir);
62 + if (err != 0)
63 + {
64 + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
65 + Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as "
66 + L"any file contained in the embedded UI package.");
67 + return uiResult;
68 + }
69 +
70 + wchar_t szConfigFilePath[MAX_PATH + 20];
71 + StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir);
72 + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config");
73 +
74 + const wchar_t* szConfigFile = szConfigFilePath;
75 + if (!PathFileExists(szConfigFilePath))
76 + {
77 + szConfigFile = NULL;
78 + }
79 +
80 + wchar_t szWIAssembly[MAX_PATH + 50];
81 + StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir);
82 + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
83 +
84 + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost))
85 + {
86 + if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir,
87 + szConfigFile, &g_pAppDomain))
88 + {
89 + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
90 + const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy";
91 + const wchar_t* szInitMethod = L"Initialize";
92 + const wchar_t* szProcessMessageMethod = L"ProcessMessage";
93 + const wchar_t* szShutdownMethod = L"Shutdown";
94 +
95 + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
96 + szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) &&
97 + GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
98 + szProxyClass, szShutdownMethod, &g_pShutdownMethod))
99 + {
100 + _MethodInfo* pInitMethod;
101 + if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
102 + szProxyClass, szInitMethod, &pInitMethod))
103 + {
104 + bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult);
105 + pInitMethod->Release();
106 + if (invokeSuccess)
107 + {
108 + if (uiResult == 0)
109 + {
110 + return ERROR_SUCCESS;
111 + }
112 + else if (uiResult == ERROR_INSTALL_USEREXIT)
113 + {
114 + // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT.
115 + // So return success here and then IDCANCEL on the next progress message.
116 + uiResult = 0;
117 + *pdwInternalUILevel = INSTALLUILEVEL_NONE;
118 + Log(hSession, L"Initialization canceled by user.");
119 + }
120 + }
121 + }
122 + }
123 +
124 + g_pProcessMessageMethod->Release();
125 + g_pProcessMessageMethod = NULL;
126 + g_pShutdownMethod->Release();
127 + g_pShutdownMethod = NULL;
128 +
129 + g_pClrHost->UnloadDomain(g_pAppDomain);
130 + g_pAppDomain->Release();
131 + g_pAppDomain = NULL;
132 + }
133 + g_pClrHost->Stop();
134 + g_pClrHost->Release();
135 + g_pClrHost = NULL;
136 + }
137 +
138 + return uiResult;
139 +}
140 +
141 +/// <summary>
142 +/// Entry-point for UI progress messages received from the MSI engine during an active installation.
143 +/// Forwards the progress messages to the managed handler method and returns its result.
144 +/// </summary>
145 +extern "C"
146 +INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord)
147 +{
148 + if (g_pProcessMessageMethod == NULL)
149 + {
150 + // Initialization was canceled.
151 + return IDCANCEL;
152 + }
153 +
154 + VARIANT vResult;
155 + VariantInit(&vResult);
156 +
157 + VARIANT vNull;
158 + vNull.vt = VT_EMPTY;
159 +
160 + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2);
161 + VARIANT vMessageType;
162 + vMessageType.vt = VT_I4;
163 + vMessageType.lVal = (LONG) uiMessageType;
164 + LONG index = 0;
165 + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType);
166 + if (FAILED(hr)) goto LExit;
167 + VARIANT vRecord;
168 + vRecord.vt = VT_I4;
169 + vRecord.lVal = (LONG) hRecord;
170 + index = 1;
171 + hr = SafeArrayPutElement(saArgs, &index, &vRecord);
172 + if (FAILED(hr)) goto LExit;
173 +
174 + hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult);
175 +
176 +LExit:
177 + SafeArrayDestroy(saArgs);
178 + if (SUCCEEDED(hr))
179 + {
180 + return vResult.intVal;
181 + }
182 + else
183 + {
184 + return -1;
185 + }
186 +}
187 +
188 +/// <summary>
189 +/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed.
190 +/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR.
191 +/// </summary>
192 +extern "C"
193 +DWORD __stdcall ShutdownEmbeddedUI()
194 +{
195 + if (g_pShutdownMethod != NULL)
196 + {
197 + VARIANT vNull;
198 + vNull.vt = VT_EMPTY;
199 + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
200 + g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL);
201 + SafeArrayDestroy(saArgs);
202 +
203 + g_pClrHost->UnloadDomain(g_pAppDomain);
204 + g_pAppDomain->Release();
205 + g_pClrHost->Stop();
206 + g_pClrHost->Release();
207 + }
208 +
209 + return 0;
210 +}
211 +
212 +/// <summary>
213 +/// Loads and invokes the managed portion of the proxy.
214 +/// </summary>
215 +/// <param name="pInitMethod">Managed initialize method to be invoked.</param>
216 +/// <param name="hSession">Handle to the installer session,
217 +/// used for logging errors and to be passed on to the managed initialize method.</param>
218 +/// <param name="szClassName">Name of the UI class to be loaded.
219 +/// This must be of the form: AssemblyName!Namespace.Class</param>
220 +/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
221 +/// the managed initialize method.</param>
222 +/// <param name="puiResult">Return value of the invoked initialize method.</param>
223 +/// <returns>True if the managed proxy was invoked successfully, or an
224 +/// error code if there was some error. Note the initialize method itself may
225 +/// return an error via puiResult while this method still returns true
226 +/// since the invocation was successful.</returns>
227 +bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult)
228 +{
229 + VARIANT vResult;
230 + VariantInit(&vResult);
231 +
232 + VARIANT vNull;
233 + vNull.vt = VT_EMPTY;
234 +
235 + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
236 + VARIANT vSessionHandle;
237 + vSessionHandle.vt = VT_I4;
238 + vSessionHandle.lVal = (LONG) hSession;
239 + LONG index = 0;
240 + HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
241 + if (FAILED(hr)) goto LExit;
242 + VARIANT vEntryPoint;
243 + vEntryPoint.vt = VT_BSTR;
244 + vEntryPoint.bstrVal = SysAllocString(szClassName);
245 + if (vEntryPoint.bstrVal == NULL)
246 + {
247 + hr = E_OUTOFMEMORY;
248 + goto LExit;
249 + }
250 + index = 1;
251 + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
252 + if (FAILED(hr)) goto LExit;
253 + VARIANT vUILevel;
254 + vUILevel.vt = VT_I4;
255 + vUILevel.ulVal = *pdwInternalUILevel;
256 + index = 2;
257 + hr = SafeArrayPutElement(saArgs, &index, &vUILevel);
258 + if (FAILED(hr)) goto LExit;
259 +
260 + hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult);
261 +
262 +LExit:
263 + SafeArrayDestroy(saArgs);
264 + if (SUCCEEDED(hr))
265 + {
266 + *puiResult = (UINT) vResult.lVal;
267 + if ((*puiResult & 0xFFFF) == 0)
268 + {
269 + // Due to interop limitations, the successful resulting UILevel is returned
270 + // as the high-word of the return value instead of via a ref parameter.
271 + *pdwInternalUILevel = *puiResult >> 16;
272 + *puiResult = 0;
273 + }
274 + return true;
275 + }
276 + else
277 + {
278 + Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr);
279 + return false;
280 + }
281 +}
src/samples/Dtf/Tools/SfxCA/EntryPoints.def new
+140
@@ -0,0 +1,140 @@
1 +; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +
4 +LIBRARY "SfxCA"
5 +
6 +EXPORTS
7 +
8 +CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000
9 +CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001
10 +CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002
11 +CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003
12 +CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004
13 +CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005
14 +CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006
15 +CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007
16 +CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008
17 +CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009
18 +CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010
19 +CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011
20 +CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012
21 +CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013
22 +CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014
23 +CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015
24 +CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016
25 +CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017
26 +CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018
27 +CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019
28 +CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020
29 +CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021
30 +CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022
31 +CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023
32 +CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024
33 +CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025
34 +CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026
35 +CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027
36 +CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028
37 +CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029
38 +CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030
39 +CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031
40 +CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032
41 +CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033
42 +CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034
43 +CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035
44 +CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036
45 +CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037
46 +CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038
47 +CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039
48 +CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040
49 +CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041
50 +CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042
51 +CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043
52 +CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044
53 +CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045
54 +CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046
55 +CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047
56 +CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048
57 +CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049
58 +CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050
59 +CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051
60 +CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052
61 +CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053
62 +CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054
63 +CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055
64 +CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056
65 +CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057
66 +CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058
67 +CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059
68 +CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060
69 +CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061
70 +CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062
71 +CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063
72 +CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064
73 +CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065
74 +CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066
75 +CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067
76 +CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068
77 +CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069
78 +CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070
79 +CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071
80 +CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072
81 +CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073
82 +CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074
83 +CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075
84 +CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076
85 +CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077
86 +CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078
87 +CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079
88 +CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080
89 +CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081
90 +CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082
91 +CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083
92 +CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084
93 +CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085
94 +CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086
95 +CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087
96 +CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088
97 +CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089
98 +CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090
99 +CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091
100 +CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092
101 +CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093
102 +CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094
103 +CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095
104 +CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096
105 +CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097
106 +CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098
107 +CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099
108 +CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100
109 +CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101
110 +CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102
111 +CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103
112 +CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104
113 +CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105
114 +CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106
115 +CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107
116 +CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108
117 +CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109
118 +CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110
119 +CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111
120 +CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112
121 +CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113
122 +CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114
123 +CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115
124 +CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116
125 +CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117
126 +CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118
127 +CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119
128 +CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120
129 +CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121
130 +CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122
131 +CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123
132 +CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124
133 +CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125
134 +CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126
135 +CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127
136 +
137 +zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc
138 +zzzInitializeEmbeddedUI=InitializeEmbeddedUI
139 +zzzEmbeddedUIHandler=EmbeddedUIHandler
140 +zzzShutdownEmbeddedUI=ShutdownEmbeddedUI
src/samples/Dtf/Tools/SfxCA/EntryPoints.h new
+162
@@ -0,0 +1,162 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +int InvokeCustomAction(MSIHANDLE hSession,
4 + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint);
5 +
6 +/// <summary>
7 +/// Macro for defining and exporting a custom action entrypoint.
8 +/// </summary>
9 +/// <param name="name">Name of the entrypoint as exported from
10 +/// the DLL.</param>
11 +/// <param name="method">Path to the managed custom action method,
12 +/// in the form: "AssemblyName!Namespace.Class.Method"</param>
13 +/// <remarks>
14 +/// To prevent the exported name from being decorated, add
15 +/// /EXPORT:name to the linker options for every entrypoint.
16 +/// </remarks>
17 +#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \
18 + name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); }
19 +
20 +// TEMPLATE ENTRYPOINTS
21 +// To be edited by the MakeSfxCA tool.
22 +
23 +#define NULLSPACE \
24 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
25 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
26 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
27 +L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
28 +
29 +#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \
30 + CustomActionEntryPoint##id##, \
31 + L"CustomActionEntryPoint" sid NULLSPACE)
32 +
33 +TEMPLATE_CA_ENTRYPOINT(000,L"000");
34 +TEMPLATE_CA_ENTRYPOINT(001,L"001");
35 +TEMPLATE_CA_ENTRYPOINT(002,L"002");
36 +TEMPLATE_CA_ENTRYPOINT(003,L"003");
37 +TEMPLATE_CA_ENTRYPOINT(004,L"004");
38 +TEMPLATE_CA_ENTRYPOINT(005,L"005");
39 +TEMPLATE_CA_ENTRYPOINT(006,L"006");
40 +TEMPLATE_CA_ENTRYPOINT(007,L"007");
41 +TEMPLATE_CA_ENTRYPOINT(008,L"008");
42 +TEMPLATE_CA_ENTRYPOINT(009,L"009");
43 +TEMPLATE_CA_ENTRYPOINT(010,L"010");
44 +TEMPLATE_CA_ENTRYPOINT(011,L"011");
45 +TEMPLATE_CA_ENTRYPOINT(012,L"012");
46 +TEMPLATE_CA_ENTRYPOINT(013,L"013");
47 +TEMPLATE_CA_ENTRYPOINT(014,L"014");
48 +TEMPLATE_CA_ENTRYPOINT(015,L"015");
49 +TEMPLATE_CA_ENTRYPOINT(016,L"016");
50 +TEMPLATE_CA_ENTRYPOINT(017,L"017");
51 +TEMPLATE_CA_ENTRYPOINT(018,L"018");
52 +TEMPLATE_CA_ENTRYPOINT(019,L"019");
53 +TEMPLATE_CA_ENTRYPOINT(020,L"020");
54 +TEMPLATE_CA_ENTRYPOINT(021,L"021");
55 +TEMPLATE_CA_ENTRYPOINT(022,L"022");
56 +TEMPLATE_CA_ENTRYPOINT(023,L"023");
57 +TEMPLATE_CA_ENTRYPOINT(024,L"024");
58 +TEMPLATE_CA_ENTRYPOINT(025,L"025");
59 +TEMPLATE_CA_ENTRYPOINT(026,L"026");
60 +TEMPLATE_CA_ENTRYPOINT(027,L"027");
61 +TEMPLATE_CA_ENTRYPOINT(028,L"028");
62 +TEMPLATE_CA_ENTRYPOINT(029,L"029");
63 +TEMPLATE_CA_ENTRYPOINT(030,L"030");
64 +TEMPLATE_CA_ENTRYPOINT(031,L"031");
65 +TEMPLATE_CA_ENTRYPOINT(032,L"032");
66 +TEMPLATE_CA_ENTRYPOINT(033,L"033");
67 +TEMPLATE_CA_ENTRYPOINT(034,L"034");
68 +TEMPLATE_CA_ENTRYPOINT(035,L"035");
69 +TEMPLATE_CA_ENTRYPOINT(036,L"036");
70 +TEMPLATE_CA_ENTRYPOINT(037,L"037");
71 +TEMPLATE_CA_ENTRYPOINT(038,L"038");
72 +TEMPLATE_CA_ENTRYPOINT(039,L"039");
73 +TEMPLATE_CA_ENTRYPOINT(040,L"040");
74 +TEMPLATE_CA_ENTRYPOINT(041,L"041");
75 +TEMPLATE_CA_ENTRYPOINT(042,L"042");
76 +TEMPLATE_CA_ENTRYPOINT(043,L"043");
77 +TEMPLATE_CA_ENTRYPOINT(044,L"044");
78 +TEMPLATE_CA_ENTRYPOINT(045,L"045");
79 +TEMPLATE_CA_ENTRYPOINT(046,L"046");
80 +TEMPLATE_CA_ENTRYPOINT(047,L"047");
81 +TEMPLATE_CA_ENTRYPOINT(048,L"048");
82 +TEMPLATE_CA_ENTRYPOINT(049,L"049");
83 +TEMPLATE_CA_ENTRYPOINT(050,L"050");
84 +TEMPLATE_CA_ENTRYPOINT(051,L"051");
85 +TEMPLATE_CA_ENTRYPOINT(052,L"052");
86 +TEMPLATE_CA_ENTRYPOINT(053,L"053");
87 +TEMPLATE_CA_ENTRYPOINT(054,L"054");
88 +TEMPLATE_CA_ENTRYPOINT(055,L"055");
89 +TEMPLATE_CA_ENTRYPOINT(056,L"056");
90 +TEMPLATE_CA_ENTRYPOINT(057,L"057");
91 +TEMPLATE_CA_ENTRYPOINT(058,L"058");
92 +TEMPLATE_CA_ENTRYPOINT(059,L"059");
93 +TEMPLATE_CA_ENTRYPOINT(060,L"060");
94 +TEMPLATE_CA_ENTRYPOINT(061,L"061");
95 +TEMPLATE_CA_ENTRYPOINT(062,L"062");
96 +TEMPLATE_CA_ENTRYPOINT(063,L"063");
97 +TEMPLATE_CA_ENTRYPOINT(064,L"064");
98 +TEMPLATE_CA_ENTRYPOINT(065,L"065");
99 +TEMPLATE_CA_ENTRYPOINT(066,L"066");
100 +TEMPLATE_CA_ENTRYPOINT(067,L"067");
101 +TEMPLATE_CA_ENTRYPOINT(068,L"068");
102 +TEMPLATE_CA_ENTRYPOINT(069,L"069");
103 +TEMPLATE_CA_ENTRYPOINT(070,L"070");
104 +TEMPLATE_CA_ENTRYPOINT(071,L"071");
105 +TEMPLATE_CA_ENTRYPOINT(072,L"072");
106 +TEMPLATE_CA_ENTRYPOINT(073,L"073");
107 +TEMPLATE_CA_ENTRYPOINT(074,L"074");
108 +TEMPLATE_CA_ENTRYPOINT(075,L"075");
109 +TEMPLATE_CA_ENTRYPOINT(076,L"076");
110 +TEMPLATE_CA_ENTRYPOINT(077,L"077");
111 +TEMPLATE_CA_ENTRYPOINT(078,L"078");
112 +TEMPLATE_CA_ENTRYPOINT(079,L"079");
113 +TEMPLATE_CA_ENTRYPOINT(080,L"080");
114 +TEMPLATE_CA_ENTRYPOINT(081,L"081");
115 +TEMPLATE_CA_ENTRYPOINT(082,L"082");
116 +TEMPLATE_CA_ENTRYPOINT(083,L"083");
117 +TEMPLATE_CA_ENTRYPOINT(084,L"084");
118 +TEMPLATE_CA_ENTRYPOINT(085,L"085");
119 +TEMPLATE_CA_ENTRYPOINT(086,L"086");
120 +TEMPLATE_CA_ENTRYPOINT(087,L"087");
121 +TEMPLATE_CA_ENTRYPOINT(088,L"088");
122 +TEMPLATE_CA_ENTRYPOINT(089,L"089");
123 +TEMPLATE_CA_ENTRYPOINT(090,L"090");
124 +TEMPLATE_CA_ENTRYPOINT(091,L"091");
125 +TEMPLATE_CA_ENTRYPOINT(092,L"092");
126 +TEMPLATE_CA_ENTRYPOINT(093,L"093");
127 +TEMPLATE_CA_ENTRYPOINT(094,L"094");
128 +TEMPLATE_CA_ENTRYPOINT(095,L"095");
129 +TEMPLATE_CA_ENTRYPOINT(096,L"096");
130 +TEMPLATE_CA_ENTRYPOINT(097,L"097");
131 +TEMPLATE_CA_ENTRYPOINT(098,L"098");
132 +TEMPLATE_CA_ENTRYPOINT(099,L"099");
133 +TEMPLATE_CA_ENTRYPOINT(100,L"100");
134 +TEMPLATE_CA_ENTRYPOINT(101,L"101");
135 +TEMPLATE_CA_ENTRYPOINT(102,L"102");
136 +TEMPLATE_CA_ENTRYPOINT(103,L"103");
137 +TEMPLATE_CA_ENTRYPOINT(104,L"104");
138 +TEMPLATE_CA_ENTRYPOINT(105,L"105");
139 +TEMPLATE_CA_ENTRYPOINT(106,L"106");
140 +TEMPLATE_CA_ENTRYPOINT(107,L"107");
141 +TEMPLATE_CA_ENTRYPOINT(108,L"108");
142 +TEMPLATE_CA_ENTRYPOINT(109,L"109");
143 +TEMPLATE_CA_ENTRYPOINT(110,L"110");
144 +TEMPLATE_CA_ENTRYPOINT(111,L"111");
145 +TEMPLATE_CA_ENTRYPOINT(112,L"112");
146 +TEMPLATE_CA_ENTRYPOINT(113,L"113");
147 +TEMPLATE_CA_ENTRYPOINT(114,L"114");
148 +TEMPLATE_CA_ENTRYPOINT(115,L"115");
149 +TEMPLATE_CA_ENTRYPOINT(116,L"116");
150 +TEMPLATE_CA_ENTRYPOINT(117,L"117");
151 +TEMPLATE_CA_ENTRYPOINT(118,L"118");
152 +TEMPLATE_CA_ENTRYPOINT(119,L"119");
153 +TEMPLATE_CA_ENTRYPOINT(120,L"120");
154 +TEMPLATE_CA_ENTRYPOINT(121,L"121");
155 +TEMPLATE_CA_ENTRYPOINT(122,L"122");
156 +TEMPLATE_CA_ENTRYPOINT(123,L"123");
157 +TEMPLATE_CA_ENTRYPOINT(124,L"124");
158 +TEMPLATE_CA_ENTRYPOINT(125,L"125");
159 +TEMPLATE_CA_ENTRYPOINT(126,L"126");
160 +TEMPLATE_CA_ENTRYPOINT(127,L"127");
161 +
162 +// Note: Keep in sync with EntryPoints.def
src/samples/Dtf/Tools/SfxCA/Extract.cpp new
+282
@@ -0,0 +1,282 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +
5 +//---------------------------------------------------------------------
6 +// CABINET EXTRACTION
7 +//---------------------------------------------------------------------
8 +
9 +// Globals make this code unsuited for multhreaded use,
10 +// but FDI doesn't provide any other way to pass context.
11 +
12 +// Handle to the FDI (cab extraction) engine. Need access to this in a callback.
13 +static HFDI g_hfdi;
14 +
15 +// FDI is not unicode-aware, so avoid passing these paths through the callbacks.
16 +static const wchar_t* g_szExtractDir;
17 +static const wchar_t* g_szCabFile;
18 +
19 +// Offset into the source file where the cabinet really starts.
20 +// Used to trick FDI into extracting from a concatenated cabinet.
21 +static int g_lCabOffset;
22 +
23 +// Use the secure CRT version of _wsopen if available.
24 +#ifdef __GOT_SECURE_LIB__
25 +#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
26 +#else
27 +#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
28 +#endif
29 +
30 +/// <summary>
31 +/// FDI callback to open a cabinet file.
32 +/// </summary>
33 +/// <param name="pszFile">Name of the file to be opened. This parameter
34 +/// is ignored since with our limited use this method is only ever called
35 +/// to open the main cabinet file.</param>
36 +/// <param name="oflag">Type of operations allowed.</param>
37 +/// <param name="pmode">Permission setting.</param>
38 +/// <returns>Integer file handle, or -1 if the file could not be opened.</returns>
39 +/// <remarks>
40 +/// To support reading from a cabinet that is concatenated onto
41 +/// another file, this function first searches for the offset of the cabinet,
42 +/// then saves that offset for use in recalculating later seeks.
43 +/// </remarks>
44 +static FNOPEN(CabOpen)
45 +{
46 + UNREFERENCED_PARAMETER(pszFile);
47 + int hf;
48 + _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
49 + if (hf != -1)
50 + {
51 + FDICABINETINFO cabInfo;
52 + int length = _lseek(hf, 0, SEEK_END);
53 + for(int offset = 0; offset < length; offset += 256)
54 + {
55 + if (_lseek(hf, offset, SEEK_SET) != offset) break;
56 + if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
57 + {
58 + g_lCabOffset = offset;
59 + _lseek(hf, offset, SEEK_SET);
60 + return hf;
61 + }
62 + }
63 + _close(hf);
64 + }
65 + return -1;
66 +}
67 +
68 +/// <summary>
69 +/// FDI callback to seek within a file.
70 +/// </summary>
71 +/// <param name="hf">File handle.</param>
72 +/// <param name="dist">Seek distance</param>
73 +/// <param name="seektype">Whether to seek relative to the
74 +/// beginning, current position, or end of the file.</param>
75 +/// <returns>Resultant position within the cabinet.</returns>
76 +/// <remarks>
77 +/// To support reading from a cabinet that is concatenated onto
78 +/// another file, this function recalculates seeks based on the
79 +/// offset that was determined when the cabinet was opened.
80 +/// </remarks>
81 +static FNSEEK(CabSeek)
82 +{
83 + if (seektype == SEEK_SET) dist += g_lCabOffset;
84 + int pos = _lseek((int) hf, dist, seektype);
85 + pos -= g_lCabOffset;
86 + return pos;
87 +}
88 +
89 +/// <summary>
90 +/// Ensures a directory and its parent directory path exists.
91 +/// </summary>
92 +/// <param name="szDirPath">Directory path, not including file name.</param>
93 +/// <returns>0 if the directory exists or was successfully created, else nonzero.</returns>
94 +/// <remarks>
95 +/// This function modifies characters in szDirPath, but always restores them
96 +/// regardless of error condition.
97 +/// </remarks>
98 +static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
99 +{
100 + int ret = 0;
101 + if (!::CreateDirectoryW(szDirPath, NULL))
102 + {
103 + UINT err = ::GetLastError();
104 + if (err != ERROR_ALREADY_EXISTS)
105 + {
106 + // Directory creation failed for some reason other than already existing.
107 + // Try to create the parent directory first.
108 + wchar_t* szLastSlash = NULL;
109 + for (wchar_t* sz = szDirPath; *sz; sz++)
110 + {
111 + if (*sz == L'\\')
112 + {
113 + szLastSlash = sz;
114 + }
115 + }
116 + if (szLastSlash)
117 + {
118 + // Temporarily take one directory off the path and recurse.
119 + *szLastSlash = L'\0';
120 + ret = EnsureDirectoryExists(szDirPath);
121 + *szLastSlash = L'\\';
122 +
123 + // Try to create the directory if all parents are created.
124 + if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
125 + {
126 + err = ::GetLastError();
127 + if (err != ERROR_ALREADY_EXISTS)
128 + {
129 + ret = -1;
130 + }
131 + }
132 + }
133 + else
134 + {
135 + ret = -1;
136 + }
137 + }
138 + }
139 + return ret;
140 +}
141 +
142 +/// <summary>
143 +/// Ensures a file's directory and its parent directory path exists.
144 +/// </summary>
145 +/// <param name="szDirPath">Path including file name.</param>
146 +/// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns>
147 +/// <remarks>
148 +/// This function modifies characters in szFilePath, but always restores them
149 +/// regardless of error condition.
150 +/// </remarks>
151 +static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
152 +{
153 + int ret = 0;
154 + wchar_t* szLastSlash = NULL;
155 + for (wchar_t* sz = szFilePath; *sz; sz++)
156 + {
157 + if (*sz == L'\\')
158 + {
159 + szLastSlash = sz;
160 + }
161 + }
162 + if (szLastSlash)
163 + {
164 + *szLastSlash = L'\0';
165 + ret = EnsureDirectoryExists(szFilePath);
166 + *szLastSlash = L'\\';
167 + }
168 + return ret;
169 +}
170 +
171 +/// <summary>
172 +/// FDI callback for handling files in the cabinet.
173 +/// </summary>
174 +/// <param name="fdint">Type of notification.</param>
175 +/// <param name="pfdin">Structure containing data about the notification.</param>
176 +/// <remarks>
177 +/// Refer to fdi.h for more comments on this notification callback.
178 +/// </remarks>
179 +static FNFDINOTIFY(CabNotification)
180 +{
181 + // fdintCOPY_FILE:
182 + // Called for each file that *starts* in the current cabinet, giving
183 + // the client the opportunity to request that the file be copied or
184 + // skipped.
185 + // Entry:
186 + // pfdin->psz1 = file name in cabinet
187 + // pfdin->cb = uncompressed size of file
188 + // pfdin->date = file date
189 + // pfdin->time = file time
190 + // pfdin->attribs = file attributes
191 + // pfdin->iFolder = file's folder index
192 + // Exit-Success:
193 + // Return non-zero file handle for destination file; FDI writes
194 + // data to this file use the PFNWRITE function supplied to FDICreate,
195 + // and then calls fdintCLOSE_FILE_INFO to close the file and set
196 + // the date, time, and attributes.
197 + // Exit-Failure:
198 + // Returns 0 => Skip file, do not copy
199 + // Returns -1 => Abort FDICopy() call
200 + if (fdint == fdintCOPY_FILE)
201 + {
202 + size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
203 + size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
204 + wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
205 + if (szFilePath == NULL) return -1;
206 + StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
207 + StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
208 + MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
209 + szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
210 + int hf = -1;
211 + if (EnsureFileDirectoryExists(szFilePath) == 0)
212 + {
213 + _wsopen__s(hf, szFilePath,
214 + _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
215 + _SH_DENYWR, _S_IREAD | _S_IWRITE);
216 + }
217 + return hf;
218 + }
219 +
220 + // fdintCLOSE_FILE_INFO:
221 + // Called after all of the data has been written to a target file.
222 + // This function must close the file and set the file date, time,
223 + // and attributes.
224 + // Entry:
225 + // pfdin->psz1 = file name in cabinet
226 + // pfdin->hf = file handle
227 + // pfdin->date = file date
228 + // pfdin->time = file time
229 + // pfdin->attribs = file attributes
230 + // pfdin->iFolder = file's folder index
231 + // pfdin->cb = Run After Extract (0 - don't run, 1 Run)
232 + // Exit-Success:
233 + // Returns TRUE
234 + // Exit-Failure:
235 + // Returns FALSE, or -1 to abort
236 + else if (fdint == fdintCLOSE_FILE_INFO)
237 + {
238 + _close((int) pfdin->hf);
239 + return TRUE;
240 + }
241 + return 0;
242 +}
243 +
244 +/// <summary>
245 +/// Extracts all contents of a cabinet file to a directory.
246 +/// </summary>
247 +/// <param name="szCabFile">Path to the cabinet file to be extracted.
248 +/// The cabinet may actually start at some offset within the file,
249 +/// as long as that offset is a multiple of 256.</param>
250 +/// <param name="szExtractDir">Directory where files are to be extracted.
251 +/// This directory must already exist, but should be empty.</param>
252 +/// <returns>0 if the cabinet was extracted successfully,
253 +/// or an error code if any error occurred.</returns>
254 +/// <remarks>
255 +/// The extraction will not overwrite any files in the destination
256 +/// directory; extraction will be interrupted and fail if any files
257 +/// with the same name already exist.
258 +/// </remarks>
259 +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
260 +{
261 + ERF erf;
262 + // Most of the FDI callbacks can be handled by existing CRT I/O functions.
263 + // For our functionality we only need to handle the open and seek callbacks.
264 + HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
265 + (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
266 + CabSeek, cpu80386, &erf);
267 + if (hfdi != NULL)
268 + {
269 + g_hfdi = hfdi;
270 + g_szCabFile = szCabFile;
271 + g_szExtractDir = szExtractDir;
272 + char szEmpty[1] = {0};
273 + if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
274 + {
275 + FDIDestroy(hfdi);
276 + return 0;
277 + }
278 + FDIDestroy(hfdi);
279 + }
280 +
281 + return erf.erfOper;
282 +}
src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp new
+629
@@ -0,0 +1,629 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +#include "RemoteMsiSession.h"
5 +
6 +
7 +//
8 +// Ensures that the request buffer is large enough to hold a request,
9 +// reallocating the buffer if necessary.
10 +// It will also reduce the buffer size if the previous allocation was very large.
11 +//
12 +static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired)
13 +{
14 + // It will also reduce the buffer size if the previous allocation was very large.
15 + if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf))
16 + {
17 + if (*pszBuf != NULL)
18 + {
19 + SecureZeroMemory(*pszBuf, *pcchBuf);
20 + delete[] *pszBuf;
21 + }
22 +
23 + *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired);
24 + *pszBuf = new wchar_t[*pcchBuf];
25 +
26 + if (*pszBuf == NULL)
27 + {
28 + return ERROR_OUTOFMEMORY;
29 + }
30 + }
31 +
32 + return ERROR_SUCCESS;
33 +}
34 +
35 +typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1);
36 +typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1);
37 +typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1);
38 +typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1);
39 +typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1);
40 +typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2);
41 +typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
42 +typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
43 +typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
44 +typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
45 +typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3);
46 +
47 +UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
48 +{
49 + int in1 = pReq->fields[0].iValue;
50 + int out1;
51 + UINT ret = (UINT) func(in1, &out1);
52 + if (ret == 0)
53 + {
54 + pResp->fields[1].vt = VT_I4;
55 + pResp->fields[1].iValue = out1;
56 + }
57 + return ret;
58 +}
59 +
60 +UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
61 +{
62 + int in1 = pReq->fields[0].iValue;
63 + int in2 = pReq->fields[1].iValue;
64 + int out1;
65 + UINT ret = (UINT) func(in1, in2, &out1);
66 + if (ret == 0)
67 + {
68 + pResp->fields[1].vt = VT_I4;
69 + pResp->fields[1].iValue = out1;
70 + }
71 + return ret;
72 +}
73 +
74 +UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
75 +{
76 + int in1 = pReq->fields[0].iValue;
77 + wchar_t* in2 = pReq->fields[1].szValue;
78 + int out1;
79 + UINT ret = (UINT) func(in1, in2, &out1);
80 + if (ret == 0)
81 + {
82 + pResp->fields[1].vt = VT_I4;
83 + pResp->fields[1].iValue = out1;
84 + }
85 + return ret;
86 +}
87 +
88 +UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
89 +{
90 + int in1 = pReq->fields[0].iValue;
91 + wchar_t* in2 = pReq->fields[1].szValue;
92 + int in3 = pReq->fields[2].iValue;
93 + int out1;
94 + UINT ret = (UINT) func(in1, in2, in3, &out1);
95 + if (ret == 0)
96 + {
97 + pResp->fields[1].vt = VT_I4;
98 + pResp->fields[1].iValue = out1;
99 + }
100 + return ret;
101 +}
102 +
103 +UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
104 +{
105 + int in1 = pReq->fields[0].iValue;
106 + wchar_t* in2 = pReq->fields[1].szValue;
107 + int in3 = pReq->fields[2].iValue;
108 + int in4 = pReq->fields[3].iValue;
109 + int out1;
110 + UINT ret = (UINT) func(in1, in2, in3, in4, &out1);
111 + if (ret == 0)
112 + {
113 + pResp->fields[1].vt = VT_I4;
114 + pResp->fields[1].iValue = out1;
115 + }
116 + return ret;
117 +}
118 +
119 +UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
120 +{
121 + int in1 = pReq->fields[0].iValue;
122 + wchar_t* in2 = pReq->fields[1].szValue;
123 + int out1, out2;
124 + UINT ret = (UINT) func(in1, in2, &out1, &out2);
125 + if (ret == 0)
126 + {
127 + pResp->fields[1].vt = VT_I4;
128 + pResp->fields[1].iValue = out1;
129 + pResp->fields[2].vt = VT_I4;
130 + pResp->fields[2].iValue = out2;
131 + }
132 + return ret;
133 +}
134 +
135 +UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
136 +{
137 + int in1 = pReq->fields[0].iValue;
138 + szBuf[0] = L'\0';
139 + DWORD cchValue = cchBuf;
140 + UINT ret = (UINT) func(in1, szBuf, &cchValue);
141 + if (ret == ERROR_MORE_DATA)
142 + {
143 + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
144 + if (ret == 0)
145 + {
146 + ret = (UINT) func(in1, szBuf, &cchValue);
147 + }
148 + }
149 + if (ret == 0)
150 + {
151 + pResp->fields[1].vt = VT_LPWSTR;
152 + pResp->fields[1].szValue = szBuf;
153 + }
154 + return ret;
155 +}
156 +
157 +MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
158 +{
159 + int in1 = pReq->fields[0].iValue;
160 + szBuf[0] = L'\0';
161 + DWORD cchValue = cchBuf;
162 + MSIDBERROR ret = func(in1, szBuf, &cchValue);
163 + if (ret == MSIDBERROR_MOREDATA)
164 + {
165 + if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue))
166 + {
167 + ret = func(in1, szBuf, &cchValue);
168 + }
169 + }
170 + if (ret != MSIDBERROR_MOREDATA)
171 + {
172 + pResp->fields[1].vt = VT_LPWSTR;
173 + pResp->fields[1].szValue = szBuf;
174 + }
175 + return ret;
176 +}
177 +
178 +UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
179 +{
180 + int in1 = pReq->fields[0].iValue;
181 + int in2 = pReq->fields[1].iValue;
182 + szBuf[0] = L'\0';
183 + DWORD cchValue = cchBuf;
184 + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
185 + if (ret == ERROR_MORE_DATA)
186 + {
187 + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
188 + if (ret == 0)
189 + {
190 + ret = (UINT) func(in1, in2, szBuf, &cchValue);
191 + }
192 + }
193 + if (ret == 0)
194 + {
195 + pResp->fields[1].vt = VT_LPWSTR;
196 + pResp->fields[1].szValue = szBuf;
197 + }
198 + return ret;
199 +}
200 +
201 +UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
202 +{
203 + int in1 = pReq->fields[0].iValue;
204 + wchar_t* in2 = pReq->fields[1].szValue;
205 + szBuf[0] = L'\0';
206 + DWORD cchValue = cchBuf;
207 + UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
208 + if (ret == ERROR_MORE_DATA)
209 + {
210 + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
211 + if (ret == 0)
212 + {
213 + ret = (UINT) func(in1, in2, szBuf, &cchValue);
214 + }
215 + }
216 + if (ret == 0)
217 + {
218 + pResp->fields[1].vt = VT_LPWSTR;
219 + pResp->fields[1].szValue = szBuf;
220 + }
221 + return ret;
222 +}
223 +
224 +UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
225 +{
226 + int in1 = pReq->fields[0].iValue;
227 + wchar_t* in2 = pReq->fields[1].szValue;
228 + int in3 = pReq->fields[2].iValue;
229 + int in4 = pReq->fields[3].iValue;
230 + szBuf[0] = L'\0';
231 + DWORD cchValue = cchBuf;
232 + int out2, out3;
233 + UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
234 + if (ret == ERROR_MORE_DATA)
235 + {
236 + ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
237 + if (ret == 0)
238 + {
239 + ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
240 + }
241 + }
242 + if (ret == 0)
243 + {
244 + pResp->fields[1].vt = VT_LPWSTR;
245 + pResp->fields[1].szValue = szBuf;
246 + pResp->fields[2].vt = VT_I4;
247 + pResp->fields[2].iValue = out2;
248 + pResp->fields[3].vt = VT_I4;
249 + pResp->fields[3].iValue = out3;
250 + }
251 + return ret;
252 +}
253 +
254 +void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp)
255 +{
256 + SecureZeroMemory(pResp, sizeof(RequestData));
257 +
258 + UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024);
259 +
260 + if (0 == ret)
261 + {
262 + switch (id)
263 + {
264 + case RemoteMsiSession::EndSession:
265 + {
266 + this->ExitCode = pReq->fields[0].iValue;
267 + }
268 + break;
269 + case RemoteMsiSession::MsiCloseHandle:
270 + {
271 + MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue;
272 + ret = ::MsiCloseHandle(h);
273 + }
274 + break;
275 + case RemoteMsiSession::MsiProcessMessage:
276 + {
277 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
278 + INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue;
279 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
280 + ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord);
281 + }
282 + break;
283 + case RemoteMsiSession::MsiGetProperty:
284 + {
285 + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend);
286 + }
287 + break;
288 + case RemoteMsiSession::MsiSetProperty:
289 + {
290 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
291 + const wchar_t* szName = pReq->fields[1].szValue;
292 + const wchar_t* szValue = pReq->fields[2].szValue;
293 + ret = ::MsiSetProperty(hInstall, szName, szValue);
294 + }
295 + break;
296 + case RemoteMsiSession::MsiCreateRecord:
297 + {
298 + UINT cParams = pReq->fields[0].uiValue;
299 + ret = ::MsiCreateRecord(cParams);
300 + }
301 + break;
302 + case RemoteMsiSession::MsiRecordGetFieldCount:
303 + {
304 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
305 + ret = ::MsiRecordGetFieldCount(hRecord);
306 + }
307 + break;
308 + case RemoteMsiSession::MsiRecordGetInteger:
309 + {
310 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
311 + UINT iField = pReq->fields[1].uiValue;
312 + ret = ::MsiRecordGetInteger(hRecord, iField);
313 + }
314 + break;
315 + case RemoteMsiSession::MsiRecordSetInteger:
316 + {
317 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
318 + UINT iField = pReq->fields[1].uiValue;
319 + int iValue = pReq->fields[2].iValue;
320 + ret = ::MsiRecordSetInteger(hRecord, iField, iValue);
321 + }
322 + break;
323 + case RemoteMsiSession::MsiRecordGetString:
324 + {
325 + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend);
326 + }
327 + break;
328 + case RemoteMsiSession::MsiRecordSetString:
329 + {
330 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
331 + UINT iField = pReq->fields[1].uiValue;
332 + const wchar_t* szValue = pReq->fields[2].szValue;
333 + ret = ::MsiRecordSetString(hRecord, iField, szValue);
334 + }
335 + break;
336 + case RemoteMsiSession::MsiRecordClearData:
337 + {
338 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
339 + ret = ::MsiRecordClearData(hRecord);
340 + }
341 + break;
342 + case RemoteMsiSession::MsiRecordIsNull:
343 + {
344 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
345 + UINT iField = pReq->fields[1].uiValue;
346 + ret = ::MsiRecordIsNull(hRecord, iField);
347 + }
348 + break;
349 + case RemoteMsiSession::MsiFormatRecord:
350 + {
351 + ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend);
352 + }
353 + break;
354 + case RemoteMsiSession::MsiGetActiveDatabase:
355 + {
356 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
357 + ret = (UINT) ::MsiGetActiveDatabase(hInstall);
358 + }
359 + break;
360 + case RemoteMsiSession::MsiDatabaseOpenView:
361 + {
362 + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp);
363 + }
364 + break;
365 + case RemoteMsiSession::MsiViewExecute:
366 + {
367 + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
368 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue;
369 + ret = ::MsiViewExecute(hView, hRecord);
370 + }
371 + break;
372 + case RemoteMsiSession::MsiViewFetch:
373 + {
374 + ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp);
375 + }
376 + break;
377 + case RemoteMsiSession::MsiViewModify:
378 + {
379 + MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
380 + MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue;
381 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
382 + ret = ::MsiViewModify(hView, eModifyMode, hRecord);
383 + }
384 + break;
385 + case RemoteMsiSession::MsiViewGetError:
386 + {
387 + ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend);
388 + }
389 + break;
390 + case RemoteMsiSession::MsiViewGetColumnInfo:
391 + {
392 + ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp);
393 + }
394 + break;
395 + case RemoteMsiSession::MsiDatabaseGetPrimaryKeys:
396 + {
397 + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp);
398 + }
399 + break;
400 + case RemoteMsiSession::MsiDatabaseIsTablePersistent:
401 + {
402 + MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue;
403 + const wchar_t* szTable = pReq->fields[1].szValue;
404 + ret = ::MsiDatabaseIsTablePersistent(hDb, szTable);
405 + }
406 + break;
407 + case RemoteMsiSession::MsiDoAction:
408 + {
409 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
410 + const wchar_t* szAction = pReq->fields[1].szValue;
411 + ret = ::MsiDoAction(hInstall, szAction);
412 + }
413 + break;
414 + case RemoteMsiSession::MsiEnumComponentCosts:
415 + {
416 + ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend);
417 + }
418 + break;
419 + case RemoteMsiSession::MsiEvaluateCondition:
420 + {
421 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
422 + const wchar_t* szCondition = pReq->fields[1].szValue;
423 + ret = ::MsiEvaluateCondition(hInstall, szCondition);
424 + }
425 + break;
426 + case RemoteMsiSession::MsiGetComponentState:
427 + {
428 + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp);
429 + }
430 + break;
431 + case RemoteMsiSession::MsiGetFeatureCost:
432 + {
433 + ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp);
434 + }
435 + break;
436 + case RemoteMsiSession::MsiGetFeatureState:
437 + {
438 + ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp);
439 + }
440 + break;
441 + case RemoteMsiSession::MsiGetFeatureValidStates:
442 + {
443 + ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp);
444 + }
445 + break;
446 + case RemoteMsiSession::MsiGetLanguage:
447 + {
448 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
449 + ret = ::MsiGetLanguage(hInstall);
450 + }
451 + break;
452 + case RemoteMsiSession::MsiGetLastErrorRecord:
453 + {
454 + ret = ::MsiGetLastErrorRecord();
455 + }
456 + break;
457 + case RemoteMsiSession::MsiGetMode:
458 + {
459 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
460 + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue;
461 + ret = ::MsiGetMode(hInstall, iRunMode);
462 + }
463 + break;
464 + case RemoteMsiSession::MsiGetSourcePath:
465 + {
466 + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend);
467 + }
468 + break;
469 + case RemoteMsiSession::MsiGetSummaryInformation:
470 + {
471 + ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp);
472 + }
473 + break;
474 + case RemoteMsiSession::MsiGetTargetPath:
475 + {
476 + ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend);
477 + }
478 + break;
479 + case RemoteMsiSession::MsiRecordDataSize:
480 + {
481 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
482 + UINT iField = pReq->fields[1].uiValue;
483 + ret = ::MsiRecordDataSize(hRecord, iField);
484 + }
485 + break;
486 + case RemoteMsiSession::MsiRecordReadStream:
487 + {
488 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
489 + UINT iField = pReq->fields[1].uiValue;
490 + DWORD cbRead = (DWORD) pReq->fields[2].uiValue;
491 + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2);
492 + if (ret == 0)
493 + {
494 + ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead);
495 + if (ret == 0)
496 + {
497 + pResp->fields[1].vt = VT_STREAM;
498 + pResp->fields[1].szValue = m_pBufSend;
499 + pResp->fields[2].vt = VT_I4;
500 + pResp->fields[2].uiValue = (UINT) cbRead;
501 + }
502 + }
503 + }
504 + break;
505 + case RemoteMsiSession::MsiRecordSetStream:
506 + {
507 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
508 + UINT iField = pReq->fields[1].uiValue;
509 + const wchar_t* szFilePath = pReq->fields[2].szValue;
510 + ret = ::MsiRecordSetStream(hRecord, iField, szFilePath);
511 + }
512 + break;
513 + case RemoteMsiSession::MsiSequence:
514 + {
515 + MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
516 + const wchar_t* szTable = pReq->fields[1].szValue;
517 + UINT iSequenceMode = pReq->fields[2].uiValue;
518 + ret = ::MsiSequence(hRecord, szTable, iSequenceMode);
519 + }
520 + break;
521 + case RemoteMsiSession::MsiSetComponentState:
522 + {
523 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
524 + const wchar_t* szComponent = pReq->fields[1].szValue;
525 + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
526 + ret = ::MsiSetComponentState(hInstall, szComponent, iState);
527 + }
528 + break;
529 + case RemoteMsiSession::MsiSetFeatureAttributes:
530 + {
531 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
532 + const wchar_t* szFeature = pReq->fields[1].szValue;
533 + DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue;
534 + ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs);
535 + }
536 + break;
537 + case RemoteMsiSession::MsiSetFeatureState:
538 + {
539 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
540 + const wchar_t* szFeature = pReq->fields[1].szValue;
541 + INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
542 + ret = ::MsiSetFeatureState(hInstall, szFeature, iState);
543 + }
544 + break;
545 + case RemoteMsiSession::MsiSetInstallLevel:
546 + {
547 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
548 + int iInstallLevel = pReq->fields[1].iValue;
549 + ret = ::MsiSetInstallLevel(hInstall, iInstallLevel);
550 + }
551 + break;
552 + case RemoteMsiSession::MsiSetMode:
553 + {
554 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
555 + MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue;
556 + BOOL fState = (BOOL) pReq->fields[2].iValue;
557 + ret = ::MsiSetMode(hInstall, iRunMode, fState);
558 + }
559 + break;
560 + case RemoteMsiSession::MsiSetTargetPath:
561 + {
562 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
563 + const wchar_t* szFolder = pReq->fields[1].szValue;
564 + const wchar_t* szFolderPath = pReq->fields[2].szValue;
565 + ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath);
566 + }
567 + break;
568 + case RemoteMsiSession::MsiSummaryInfoGetProperty:
569 + {
570 + MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue;
571 + UINT uiProperty = pReq->fields[1].uiValue;
572 + UINT uiDataType;
573 + int iValue;
574 + FILETIME ftValue;
575 + m_pBufSend[0] = L'\0';
576 + DWORD cchValue = m_cbBufSend;
577 + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
578 + if (ret == ERROR_MORE_DATA)
579 + {
580 + ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue);
581 + if (ret == 0)
582 + {
583 + ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
584 + }
585 + }
586 + if (ret == 0)
587 + {
588 + pResp->fields[1].vt = VT_UI4;
589 + pResp->fields[1].uiValue = uiDataType;
590 +
591 + switch (uiDataType)
592 + {
593 + case VT_I2:
594 + case VT_I4:
595 + pResp->fields[2].vt = VT_I4;
596 + pResp->fields[2].iValue = iValue;
597 + break;
598 + case VT_FILETIME:
599 + pResp->fields[2].vt = VT_UI4;
600 + pResp->fields[2].iValue = ftValue.dwHighDateTime;
601 + pResp->fields[3].vt = VT_UI4;
602 + pResp->fields[3].iValue = ftValue.dwLowDateTime;
603 + break;
604 + case VT_LPSTR:
605 + pResp->fields[2].vt = VT_LPWSTR;
606 + pResp->fields[2].szValue = m_pBufSend;
607 + break;
608 + }
609 + }
610 + }
611 + break;
612 + case RemoteMsiSession::MsiVerifyDiskSpace:
613 + {
614 + MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
615 + ret = ::MsiVerifyDiskSpace(hInstall);
616 + }
617 + break;
618 +
619 + default:
620 + {
621 + ret = ERROR_INVALID_FUNCTION;
622 + }
623 + break;
624 + }
625 + }
626 +
627 + pResp->fields[0].vt = VT_UI4;
628 + pResp->fields[0].uiValue = ret;
629 +}
src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h new
+898
@@ -0,0 +1,898 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#define LARGE_BUFFER_THRESHOLD 65536 // bytes
4 +#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts
5 +
6 +///////////////////////////////////////////////////////////////////////////////////////
7 +// RemoteMsiSession //
8 +//////////////////////
9 +//
10 +// Allows accessing MSI APIs from another process using named pipes.
11 +//
12 +class RemoteMsiSession
13 +{
14 +public:
15 +
16 + // This enumeration MUST stay in sync with the
17 + // managed equivalent in RemotableNativeMethods.cs!
18 + enum RequestId
19 + {
20 + EndSession = 0,
21 + MsiCloseHandle,
22 + MsiCreateRecord,
23 + MsiDatabaseGetPrimaryKeys,
24 + MsiDatabaseIsTablePersistent,
25 + MsiDatabaseOpenView,
26 + MsiDoAction,
27 + MsiEnumComponentCosts,
28 + MsiEvaluateCondition,
29 + MsiFormatRecord,
30 + MsiGetActiveDatabase,
31 + MsiGetComponentState,
32 + MsiGetFeatureCost,
33 + MsiGetFeatureState,
34 + MsiGetFeatureValidStates,
35 + MsiGetLanguage,
36 + MsiGetLastErrorRecord,
37 + MsiGetMode,
38 + MsiGetProperty,
39 + MsiGetSourcePath,
40 + MsiGetSummaryInformation,
41 + MsiGetTargetPath,
42 + MsiProcessMessage,
43 + MsiRecordClearData,
44 + MsiRecordDataSize,
45 + MsiRecordGetFieldCount,
46 + MsiRecordGetInteger,
47 + MsiRecordGetString,
48 + MsiRecordIsNull,
49 + MsiRecordReadStream,
50 + MsiRecordSetInteger,
51 + MsiRecordSetStream,
52 + MsiRecordSetString,
53 + MsiSequence,
54 + MsiSetComponentState,
55 + MsiSetFeatureAttributes,
56 + MsiSetFeatureState,
57 + MsiSetInstallLevel,
58 + MsiSetMode,
59 + MsiSetProperty,
60 + MsiSetTargetPath,
61 + MsiSummaryInfoGetProperty,
62 + MsiVerifyDiskSpace,
63 + MsiViewExecute,
64 + MsiViewFetch,
65 + MsiViewGetError,
66 + MsiViewGetColumnInfo,
67 + MsiViewModify,
68 + };
69 +
70 + static const int MAX_REQUEST_FIELDS = 4;
71 +
72 + // Used to pass data back and forth for remote API calls,
73 + // including in & out params & return values.
74 + // Only strings and ints are supported.
75 + struct RequestData
76 + {
77 + struct
78 + {
79 + VARENUM vt;
80 + union {
81 + int iValue;
82 + UINT uiValue;
83 + DWORD cchValue;
84 + LPWSTR szValue;
85 + BYTE* sValue;
86 + DWORD cbValue;
87 + };
88 + } fields[MAX_REQUEST_FIELDS];
89 + };
90 +
91 +public:
92 +
93 + // This value is set from the single data parameter in the EndSession request.
94 + // It saves the exit code of the out-of-proc custom action.
95 + int ExitCode;
96 +
97 + /////////////////////////////////////////////////////////////////////////////////////
98 + // RemoteMsiSession constructor
99 + //
100 + // Creates a new remote session instance, for use either by the server
101 + // or client process.
102 + //
103 + // szName - Identifies the session instance being remoted. The server and
104 + // the client must use the same name. The name should be unique
105 + // enough to avoid conflicting with other instances on the system.
106 + //
107 + // fServer - True if the calling process is the server process, false if the
108 + // calling process is the client process.
109 + //
110 + RemoteMsiSession(const wchar_t* szName, bool fServer=true)
111 + : m_fServer(fServer),
112 + m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"),
113 + m_szPipeName(NULL),
114 + m_hPipe(NULL),
115 + m_fConnecting(false),
116 + m_fConnected(false),
117 + m_hReceiveThread(NULL),
118 + m_hReceiveStopEvent(NULL),
119 + m_pBufReceive(NULL),
120 + m_cbBufReceive(0),
121 + m_pBufSend(NULL),
122 + m_cbBufSend(0),
123 + ExitCode(ERROR_INSTALL_FAILURE)
124 + {
125 + SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED));
126 + m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
127 + }
128 +
129 + /////////////////////////////////////////////////////////////////////////////////////
130 + // RemoteMsiSession destructor
131 + //
132 + // Closes any open handles and frees any allocated memory.
133 + //
134 + ~RemoteMsiSession()
135 + {
136 + WaitExitCode();
137 + if (m_hPipe != NULL)
138 + {
139 + CloseHandle(m_hPipe);
140 + m_hPipe = NULL;
141 + }
142 + if (m_overlapped.hEvent != NULL)
143 + {
144 + CloseHandle(m_overlapped.hEvent);
145 + m_overlapped.hEvent = NULL;
146 + }
147 + if (m_szPipeName != NULL)
148 + {
149 + delete[] m_szPipeName;
150 + m_szPipeName = NULL;
151 + }
152 + if (m_pBufReceive != NULL)
153 + {
154 + SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
155 + delete[] m_pBufReceive;
156 + m_pBufReceive = NULL;
157 + }
158 + if (m_pBufSend != NULL)
159 + {
160 + SecureZeroMemory(m_pBufSend, m_cbBufSend);
161 + delete[] m_pBufSend;
162 + m_pBufSend = NULL;
163 + }
164 + m_fConnecting = false;
165 + m_fConnected = false;
166 + }
167 +
168 + /////////////////////////////////////////////////////////////////////////////////////
169 + // RemoteMsiSession::WaitExitCode()
170 + //
171 + // Waits for the server processing thread to complete.
172 + //
173 + void WaitExitCode()
174 + {
175 + if (m_hReceiveThread != NULL)
176 + {
177 + SetEvent(m_hReceiveStopEvent);
178 + WaitForSingleObject(m_hReceiveThread, INFINITE);
179 + CloseHandle(m_hReceiveThread);
180 + m_hReceiveThread = NULL;
181 + }
182 + }
183 +
184 + /////////////////////////////////////////////////////////////////////////////////////
185 + // RemoteMsiSession::Connect()
186 + //
187 + // Connects the inter-process communication channel.
188 + // (Currently implemented as a named pipe.)
189 + //
190 + // This method must be called first by the server process, then by the client
191 + // process. The method does not block; the server will asynchronously wait
192 + // for the client process to make the connection.
193 + //
194 + // Returns: 0 on success, Win32 error code on failure.
195 + //
196 + virtual DWORD Connect()
197 + {
198 + const wchar_t* szPipePrefix = L"\\\\.\\pipe\\";
199 + size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1;
200 + m_szPipeName = new wchar_t[cchPipeNameBuf];
201 +
202 + if (m_szPipeName == NULL)
203 + {
204 + return ERROR_OUTOFMEMORY;
205 + }
206 + else
207 + {
208 + wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix);
209 + wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName);
210 +
211 + if (m_fServer)
212 + {
213 + return this->ConnectPipeServer();
214 + }
215 + else
216 + {
217 + return this->ConnectPipeClient();
218 + }
219 + }
220 + }
221 +
222 + /////////////////////////////////////////////////////////////////////////////////////
223 + // RemoteMsiSession::IsConnected()
224 + //
225 + // Checks if the server process and client process are currently connected.
226 + //
227 + virtual bool IsConnected() const
228 + {
229 + return m_fConnected;
230 + }
231 +
232 + /////////////////////////////////////////////////////////////////////////////////////
233 + // RemoteMsiSession::ProcessRequests()
234 + //
235 + // For use by the service process. Watches for requests in the input buffer and calls
236 + // the callback for each one.
237 + //
238 + // This method does not block; it spawns a separate thread to do the work.
239 + //
240 + // Returns: 0 on success, Win32 error code on failure.
241 + //
242 + virtual DWORD ProcessRequests()
243 + {
244 + return this->StartProcessingReqests();
245 + }
246 +
247 + /////////////////////////////////////////////////////////////////////////////////////
248 + // RemoteMsiSession::SendRequest()
249 + //
250 + // For use by the client process. Sends a request to the server and
251 + // synchronously waits on a response, up to the timeout value.
252 + //
253 + // id - ID code of the MSI API call being requested.
254 + //
255 + // pRequest - Pointer to a data structure containing request parameters.
256 + //
257 + // ppResponse - [OUT] Pointer to a location that receives the response parameters.
258 + //
259 + // Returns: 0 on success, Win32 error code on failure.
260 + // Returns WAIT_TIMEOUT if no response was received in time.
261 + //
262 + virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse)
263 + {
264 + if (m_fServer)
265 + {
266 + return ERROR_INVALID_OPERATION;
267 + }
268 +
269 + if (!m_fConnected)
270 + {
271 + *ppResponse = NULL;
272 + return 0;
273 + }
274 +
275 + DWORD dwRet = this->SendRequest(id, pRequest);
276 + if (dwRet != 0)
277 + {
278 + return dwRet;
279 + }
280 +
281 + if (id != EndSession)
282 + {
283 + static RequestData response;
284 + if (ppResponse != NULL)
285 + {
286 + *ppResponse = &response;
287 + }
288 +
289 + return this->ReceiveResponse(id, &response);
290 + }
291 + else
292 + {
293 + CloseHandle(m_hPipe);
294 + m_hPipe = NULL;
295 + m_fConnected = false;
296 + return 0;
297 + }
298 + }
299 +
300 +private:
301 +
302 + //
303 + // Do not allow assignment.
304 + //
305 + RemoteMsiSession& operator=(const RemoteMsiSession&);
306 +
307 + //
308 + // Called only by the server process.
309 + // Create a new thread to handle receiving requests.
310 + //
311 + DWORD StartProcessingReqests()
312 + {
313 + if (!m_fServer || m_hReceiveStopEvent != NULL)
314 + {
315 + return ERROR_INVALID_OPERATION;
316 + }
317 +
318 + DWORD dwRet = 0;
319 +
320 + m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
321 +
322 + if (m_hReceiveStopEvent == NULL)
323 + {
324 + dwRet = GetLastError();
325 + }
326 + else
327 + {
328 + if (m_hReceiveThread != NULL)
329 + {
330 + CloseHandle(m_hReceiveThread);
331 + }
332 +
333 + m_hReceiveThread = CreateThread(NULL, 0,
334 + RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL);
335 +
336 + if (m_hReceiveThread == NULL)
337 + {
338 + dwRet = GetLastError();
339 + CloseHandle(m_hReceiveStopEvent);
340 + m_hReceiveStopEvent = NULL;
341 + }
342 + }
343 +
344 + return dwRet;
345 + }
346 +
347 + //
348 + // Called only by the watcher process.
349 + // First verify the connection is complete. Then continually read and parse messages,
350 + // invoke the callback, and send the replies.
351 + //
352 + static DWORD WINAPI ProcessRequestsThreadStatic(void* pv)
353 + {
354 + return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread();
355 + }
356 +
357 + DWORD ProcessRequestsThread()
358 + {
359 + DWORD dwRet;
360 +
361 + dwRet = CompleteConnection();
362 + if (dwRet != 0)
363 + {
364 + if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0;
365 + }
366 +
367 + while (m_fConnected)
368 + {
369 + RequestId id;
370 + RequestData req;
371 + dwRet = ReceiveRequest(&id, &req);
372 + if (dwRet != 0)
373 + {
374 + if (dwRet == ERROR_OPERATION_ABORTED ||
375 + dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA)
376 + {
377 + dwRet = 0;
378 + }
379 + }
380 + else
381 + {
382 + RequestData resp;
383 + ProcessRequest(id, &req, &resp);
384 +
385 + if (id == EndSession)
386 + {
387 + break;
388 + }
389 +
390 + dwRet = SendResponse(id, &resp);
391 + if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA)
392 + {
393 + dwRet = 0;
394 + }
395 + }
396 + }
397 +
398 + CloseHandle(m_hReceiveStopEvent);
399 + m_hReceiveStopEvent = NULL;
400 + return dwRet;
401 + }
402 +
403 + //
404 + // Called only by the server process's receive thread.
405 + // Read one request into a RequestData object.
406 + //
407 + DWORD ReceiveRequest(RequestId* pId, RequestData* pReq)
408 + {
409 + DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId));
410 +
411 + if (dwRet == 0)
412 + {
413 + dwRet = this->ReadRequestData(pReq);
414 + }
415 +
416 + return dwRet;
417 + }
418 +
419 + //
420 + // Called by the server process's receive thread or the client's request call
421 + // to read the response. Read data from the pipe, allowing interruption by the
422 + // stop event if on the server.
423 + //
424 + DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead)
425 + {
426 + DWORD dwRet = 0;
427 + DWORD dwTotalBytesRead = 0;
428 +
429 + while (dwRet == 0 && dwTotalBytesRead < cbRead)
430 + {
431 + DWORD dwBytesReadThisTime;
432 + ResetEvent(m_overlapped.hEvent);
433 + if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped))
434 + {
435 + dwRet = GetLastError();
436 + if (dwRet == ERROR_IO_PENDING)
437 + {
438 + if (m_fServer)
439 + {
440 + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
441 + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
442 + }
443 + else
444 + {
445 + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
446 + }
447 +
448 + if (dwRet == WAIT_OBJECT_0)
449 + {
450 + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE))
451 + {
452 + dwRet = GetLastError();
453 + }
454 + }
455 + else if (dwRet == WAIT_FAILED)
456 + {
457 + dwRet = GetLastError();
458 + }
459 + else
460 + {
461 + dwRet = ERROR_OPERATION_ABORTED;
462 + }
463 + }
464 + }
465 +
466 + dwTotalBytesRead += dwBytesReadThisTime;
467 + }
468 +
469 + if (dwRet != 0)
470 + {
471 + if (m_fServer)
472 + {
473 + CancelIo(m_hPipe);
474 + DisconnectNamedPipe(m_hPipe);
475 + }
476 + else
477 + {
478 + CloseHandle(m_hPipe);
479 + m_hPipe = NULL;
480 + }
481 + m_fConnected = false;
482 + }
483 +
484 + return dwRet;
485 + }
486 +
487 + //
488 + // Called only by the server process.
489 + // Given a request, invoke the MSI API and return the response.
490 + // This is implemented in RemoteMsi.cpp.
491 + //
492 + void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp);
493 +
494 + //
495 + // Called only by the client process.
496 + // Send request data over the pipe.
497 + //
498 + DWORD SendRequest(RequestId id, const RequestData* pRequest)
499 + {
500 + DWORD dwRet = WriteRequestData(id, pRequest);
501 +
502 + if (dwRet != 0)
503 + {
504 + m_fConnected = false;
505 + CloseHandle(m_hPipe);
506 + m_hPipe = NULL;
507 + }
508 +
509 + return dwRet;
510 + }
511 +
512 + //
513 + // Called only by the server process.
514 + // Just send a response over the pipe.
515 + //
516 + DWORD SendResponse(RequestId id, const RequestData* pResp)
517 + {
518 + DWORD dwRet = WriteRequestData(id, pResp);
519 +
520 + if (dwRet != 0)
521 + {
522 + DisconnectNamedPipe(m_hPipe);
523 + m_fConnected = false;
524 + }
525 +
526 + return dwRet;
527 + }
528 +
529 + //
530 + // Called either by the client or server process.
531 + // Writes data to the pipe for a request or response.
532 + //
533 + DWORD WriteRequestData(RequestId id, const RequestData* pReq)
534 + {
535 + DWORD dwRet = 0;
536 +
537 + RequestData req = *pReq; // Make a copy because the const data can't be changed.
538 +
539 + dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId));
540 + if (dwRet != 0)
541 + {
542 + return dwRet;
543 + }
544 +
545 + BYTE* sValues[MAX_REQUEST_FIELDS] = {0};
546 + for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
547 + {
548 + if (req.fields[i].vt == VT_LPWSTR)
549 + {
550 + sValues[i] = (BYTE*) req.fields[i].szValue;
551 + req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue);
552 + }
553 + else if (req.fields[i].vt == VT_STREAM)
554 + {
555 + sValues[i] = req.fields[i].sValue;
556 + req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue;
557 + }
558 + }
559 +
560 + dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData));
561 + if (dwRet != 0)
562 + {
563 + return dwRet;
564 + }
565 +
566 + for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
567 + {
568 + if (sValues[i] != NULL)
569 + {
570 + DWORD cbValue;
571 + if (req.fields[i].vt == VT_LPWSTR)
572 + {
573 + cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR);
574 + }
575 + else
576 + {
577 + cbValue = req.fields[i].cbValue;
578 + }
579 +
580 + dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue);
581 + if (dwRet != 0)
582 + {
583 + break;
584 + }
585 + }
586 + }
587 +
588 + return dwRet;
589 + }
590 +
591 + //
592 + // Called when writing a request or response. Writes data to
593 + // the pipe, allowing interruption by the stop event if on the server.
594 + //
595 + DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite)
596 + {
597 + DWORD dwRet = 0;
598 + DWORD dwTotalBytesWritten = 0;
599 +
600 + while (dwRet == 0 && dwTotalBytesWritten < cbWrite)
601 + {
602 + DWORD dwBytesWrittenThisTime;
603 + ResetEvent(m_overlapped.hEvent);
604 + if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped))
605 + {
606 + dwRet = GetLastError();
607 + if (dwRet == ERROR_IO_PENDING)
608 + {
609 + if (m_fServer)
610 + {
611 + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
612 + dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
613 + }
614 + else
615 + {
616 + dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
617 + }
618 +
619 + if (dwRet == WAIT_OBJECT_0)
620 + {
621 + if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE))
622 + {
623 + dwRet = GetLastError();
624 + }
625 + }
626 + else if (dwRet == WAIT_FAILED)
627 + {
628 + dwRet = GetLastError();
629 + }
630 + else
631 + {
632 + dwRet = ERROR_OPERATION_ABORTED;
633 + }
634 + }
635 + }
636 +
637 + dwTotalBytesWritten += dwBytesWrittenThisTime;
638 + }
639 +
640 + return dwRet;
641 + }
642 +
643 + //
644 + // Called either by the client or server process.
645 + // Reads data from the pipe for a request or response.
646 + //
647 + DWORD ReadRequestData(RequestData* pReq)
648 + {
649 + DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData));
650 +
651 + if (dwRet == 0)
652 + {
653 + DWORD cbData = 0;
654 + for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
655 + {
656 + if (pReq->fields[i].vt == VT_LPWSTR)
657 + {
658 + cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
659 + }
660 + else if (pReq->fields[i].vt == VT_STREAM)
661 + {
662 + cbData += pReq->fields[i].cbValue;
663 + }
664 + }
665 +
666 + if (cbData > 0)
667 + {
668 + if (!CheckRequestDataBuf(cbData))
669 + {
670 + return ERROR_OUTOFMEMORY;
671 + }
672 +
673 + dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData);
674 + if (dwRet == 0)
675 + {
676 + DWORD dwOffset = 0;
677 + for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
678 + {
679 + if (pReq->fields[i].vt == VT_LPWSTR)
680 + {
681 + LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset);
682 + dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
683 + pReq->fields[i].szValue = szTemp;
684 + }
685 + else if (pReq->fields[i].vt == VT_STREAM)
686 + {
687 + BYTE* sTemp = m_pBufReceive + dwOffset;
688 + dwOffset += pReq->fields[i].cbValue;
689 + pReq->fields[i].sValue = sTemp;
690 + }
691 + }
692 + }
693 + }
694 + }
695 +
696 + return dwRet;
697 + }
698 +
699 + //
700 + // Called only by the client process.
701 + // Wait for a response on the pipe. If no response is received before the timeout,
702 + // then give up and close the connection.
703 + //
704 + DWORD ReceiveResponse(RequestId id, RequestData* pResp)
705 + {
706 + RequestId responseId;
707 + DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId));
708 + if (dwRet == 0 && responseId != id)
709 + {
710 + dwRet = ERROR_OPERATION_ABORTED;
711 + }
712 +
713 + if (dwRet == 0)
714 + {
715 + dwRet = this->ReadRequestData(pResp);
716 + }
717 +
718 + return dwRet;
719 + }
720 +
721 + //
722 + // Called only by the server process's receive thread.
723 + // Try to complete and verify an asynchronous connection operation.
724 + //
725 + DWORD CompleteConnection()
726 + {
727 + DWORD dwRet = 0;
728 + if (m_fConnecting)
729 + {
730 + HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
731 + DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
732 +
733 + if (dwWaitRes == WAIT_OBJECT_0)
734 + {
735 + m_fConnecting = false;
736 +
737 + DWORD dwUnused;
738 + if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE))
739 + {
740 + m_fConnected = true;
741 + }
742 + else
743 + {
744 + dwRet = GetLastError();
745 + }
746 + }
747 + else if (dwWaitRes == WAIT_FAILED)
748 + {
749 + CancelIo(m_hPipe);
750 + dwRet = GetLastError();
751 + }
752 + else
753 + {
754 + CancelIo(m_hPipe);
755 + dwRet = ERROR_OPERATION_ABORTED;
756 + }
757 + }
758 + return dwRet;
759 + }
760 +
761 + //
762 + // Called only by the server process.
763 + // Creates a named pipe instance and begins asynchronously waiting
764 + // for a connection from the client process.
765 + //
766 + DWORD ConnectPipeServer()
767 + {
768 + DWORD dwRet = 0;
769 + const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes
770 + m_hPipe = CreateNamedPipe(
771 + m_szPipeName,
772 + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
773 + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
774 + 1, BUFSIZE, BUFSIZE, 0, NULL);
775 + if (m_hPipe == INVALID_HANDLE_VALUE)
776 + {
777 + m_hPipe = NULL;
778 + dwRet = GetLastError();
779 + }
780 + else if (ConnectNamedPipe(m_hPipe, &m_overlapped))
781 + {
782 + m_fConnected = true;
783 + }
784 + else
785 + {
786 + dwRet = GetLastError();
787 +
788 + if (dwRet == ERROR_PIPE_BUSY)
789 + {
790 + // All pipe instances are busy, so wait for a maximum of 20 seconds
791 + dwRet = 0;
792 + if (WaitNamedPipe(m_szPipeName, 20000))
793 + {
794 + m_fConnected = true;
795 + }
796 + else
797 + {
798 + dwRet = GetLastError();
799 + }
800 + }
801 +
802 + if (dwRet == ERROR_IO_PENDING)
803 + {
804 + dwRet = 0;
805 + m_fConnecting = true;
806 + }
807 + }
808 + return dwRet;
809 + }
810 +
811 + //
812 + // Called only by the client process.
813 + // Attemps to open a connection to an existing named pipe instance
814 + // which should have already been created by the server process.
815 + //
816 + DWORD ConnectPipeClient()
817 + {
818 + DWORD dwRet = 0;
819 + m_hPipe = CreateFile(
820 + m_szPipeName, GENERIC_READ | GENERIC_WRITE,
821 + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
822 + if (m_hPipe != INVALID_HANDLE_VALUE)
823 + {
824 + m_fConnected = true;
825 + }
826 + else
827 + {
828 + m_hPipe = NULL;
829 + dwRet = GetLastError();
830 + }
831 + return dwRet;
832 + }
833 +
834 + //
835 + // Ensures that the request buffer is large enough to hold a request,
836 + // reallocating the buffer if necessary.
837 + // It will also reduce the buffer size if the previous allocation was very large.
838 + //
839 + BOOL CheckRequestDataBuf(DWORD cbBuf)
840 + {
841 + if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive))
842 + {
843 + if (m_pBufReceive != NULL)
844 + {
845 + SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
846 + delete[] m_pBufReceive;
847 + }
848 + m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf);
849 + m_pBufReceive = new BYTE[m_cbBufReceive];
850 + if (m_pBufReceive == NULL)
851 + {
852 + m_cbBufReceive = 0;
853 + }
854 + }
855 + return m_pBufReceive != NULL;
856 + }
857 +
858 +private:
859 +
860 + // Name of this instance.
861 + const wchar_t* m_szName;
862 +
863 + // "\\.\pipe\name"
864 + wchar_t* m_szPipeName;
865 +
866 + // Handle to the pipe instance.
867 + HANDLE m_hPipe;
868 +
869 + // Handle to the thread that receives requests.
870 + HANDLE m_hReceiveThread;
871 +
872 + // Handle to the event used to signal the receive thread to exit.
873 + HANDLE m_hReceiveStopEvent;
874 +
875 + // All pipe I/O is done in overlapped mode to avoid unintentional blocking.
876 + OVERLAPPED m_overlapped;
877 +
878 + // Dynamically-resized buffer for receiving requests.
879 + BYTE* m_pBufReceive;
880 +
881 + // Current size of the receive request buffer.
882 + DWORD m_cbBufReceive;
883 +
884 + // Dynamically-resized buffer for sending requests.
885 + wchar_t* m_pBufSend;
886 +
887 + // Current size of the send request buffer.
888 + DWORD m_cbBufSend;
889 +
890 + // True if this is the server process, false if this is the client process.
891 + const bool m_fServer;
892 +
893 + // True if an asynchronous connection operation is currently in progress.
894 + bool m_fConnecting;
895 +
896 + // True if the pipe is currently connected.
897 + bool m_fConnected;
898 +};
src/samples/Dtf/Tools/SfxCA/SfxCA.cpp new
+363
@@ -0,0 +1,363 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +#include "EntryPoints.h"
5 +#include "SfxUtil.h"
6 +
7 +#define MANAGED_CAs_OUT_OF_PROC 1
8 +
9 +HMODULE g_hModule;
10 +bool g_fRunningOutOfProc = false;
11 +
12 +RemoteMsiSession* g_pRemote = NULL;
13 +
14 +// Prototypes for local functions.
15 +// See the function definitions for comments.
16 +
17 +bool InvokeManagedCustomAction(MSIHANDLE hSession,
18 + _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult);
19 +
20 +/// <summary>
21 +/// Entry-point for the CA DLL when re-launched as a separate process;
22 +/// connects the comm channel for remote MSI APIs, then invokes the
23 +/// managed custom action entry-point.
24 +/// </summary>
25 +/// <remarks>
26 +/// Do not change the parameters or calling-convention: RUNDLL32
27 +/// requires this exact signature.
28 +/// </remarks>
29 +extern "C"
30 +void __stdcall InvokeManagedCustomActionOutOfProc(
31 + __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow)
32 +{
33 + UNREFERENCED_PARAMETER(hwnd);
34 + UNREFERENCED_PARAMETER(hinst);
35 + UNREFERENCED_PARAMETER(nCmdShow);
36 +
37 + g_fRunningOutOfProc = true;
38 +
39 + const wchar_t* szSessionName = szCmdLine;
40 + MSIHANDLE hSession;
41 + const wchar_t* szEntryPoint;
42 +
43 + int i;
44 + for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++);
45 + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
46 + hSession = _wtoi(szCmdLine + i);
47 +
48 + for (; szCmdLine[i] && szCmdLine[i] != L' '; i++);
49 + if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
50 + szEntryPoint = szCmdLine + i;
51 +
52 + g_pRemote = new RemoteMsiSession(szSessionName, false);
53 + g_pRemote->Connect();
54 +
55 + int ret = InvokeCustomAction(hSession, NULL, szEntryPoint);
56 +
57 + RemoteMsiSession::RequestData requestData;
58 + SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData));
59 + requestData.fields[0].vt = VT_I4;
60 + requestData.fields[0].iValue = ret;
61 + g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL);
62 + delete g_pRemote;
63 +}
64 +
65 +/// <summary>
66 +/// Re-launch this CA DLL as a separate process, and setup a comm channel
67 +/// for remote MSI API calls back to this process.
68 +/// </summary>
69 +int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint)
70 +{
71 + wchar_t szSessionName[100] = {0};
72 + swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount());
73 +
74 + RemoteMsiSession remote(szSessionName, true);
75 +
76 + DWORD ret = remote.Connect();
77 + if (ret != 0)
78 + {
79 + Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret);
80 + return ERROR_INSTALL_FAILURE;
81 + }
82 +
83 + ret = remote.ProcessRequests();
84 + if (ret != 0)
85 + {
86 + Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret);
87 + return ERROR_INSTALL_FAILURE;
88 + }
89 +
90 + wchar_t szModule[MAX_PATH] = {0};
91 + GetModuleFileName(g_hModule, szModule, MAX_PATH);
92 +
93 + const wchar_t* rundll32 = L"rundll32.exe";
94 + wchar_t szRunDll32Path[MAX_PATH] = {0};
95 + GetSystemDirectory(szRunDll32Path, MAX_PATH);
96 + wcscat_s(szRunDll32Path, MAX_PATH, L"\\");
97 + wcscat_s(szRunDll32Path, MAX_PATH, rundll32);
98 +
99 + const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc";
100 + wchar_t szCommandLine[1024] = {0};
101 + swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s",
102 + rundll32, szModule, entry, szSessionName, hSession, szEntryPoint);
103 +
104 + STARTUPINFO si;
105 + SecureZeroMemory(&si, sizeof(STARTUPINFO));
106 + si.cb = sizeof(STARTUPINFO);
107 +
108 + PROCESS_INFORMATION pi;
109 + SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
110 +
111 + if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE,
112 + 0, NULL, NULL, &si, &pi))
113 + {
114 + DWORD err = GetLastError();
115 + Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err);
116 + return ERROR_INSTALL_FAILURE;
117 + }
118 +
119 + DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE);
120 + if (dwWait != WAIT_OBJECT_0)
121 + {
122 + DWORD err = GetLastError();
123 + Log(hSession, L"Failed to wait for CA process. Error code: %d", err);
124 + return ERROR_INSTALL_FAILURE;
125 + }
126 +
127 + DWORD dwExitCode;
128 + BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode);
129 + if (!bRet)
130 + {
131 + DWORD err = GetLastError();
132 + Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err);
133 + return ERROR_INSTALL_FAILURE;
134 + }
135 + else if (dwExitCode != 0)
136 + {
137 + Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode);
138 + return ERROR_INSTALL_FAILURE;
139 + }
140 +
141 + CloseHandle(pi.hThread);
142 + CloseHandle(pi.hProcess);
143 +
144 + remote.WaitExitCode();
145 + return remote.ExitCode;
146 +}
147 +
148 +/// <summary>
149 +/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to
150 +/// call MSI APIs remotely.
151 +/// </summary>
152 +void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse)
153 +{
154 + if (g_fRunningOutOfProc)
155 + {
156 + g_pRemote->SendRequest(id, pRequest, ppResponse);
157 + }
158 + else
159 + {
160 + *ppResponse = NULL;
161 + }
162 +}
163 +
164 +/// <summary>
165 +/// Invokes a managed custom action from native code by
166 +/// extracting the package to a temporary working directory
167 +/// then hosting the CLR and locating and calling the entrypoint.
168 +/// </summary>
169 +/// <param name="hSession">Handle to the installation session.
170 +/// Passed to custom action entrypoints by the installer engine.</param>
171 +/// <param name="szWorkingDir">Directory containing the CA binaries
172 +/// and the CustomAction.config file defining the entrypoints.
173 +/// This may be NULL, in which case the current module must have
174 +/// a concatenated cabinet containing those files, which will be
175 +/// extracted to a temporary directory.</param>
176 +/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
177 +/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
178 +/// string, or a simple name that maps to a full entrypoint definition
179 +/// in CustomAction.config.</param>
180 +/// <returns>The value returned by the managed custom action method,
181 +/// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns>
182 +int InvokeCustomAction(MSIHANDLE hSession,
183 + const wchar_t* szWorkingDir, const wchar_t* szEntryPoint)
184 +{
185 +#ifdef MANAGED_CAs_OUT_OF_PROC
186 + if (!g_fRunningOutOfProc && szWorkingDir == NULL)
187 + {
188 + return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint);
189 + }
190 +#endif
191 +
192 + wchar_t szTempDir[MAX_PATH];
193 + bool fDeleteTemp = false;
194 + if (szWorkingDir == NULL)
195 + {
196 + if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH))
197 + {
198 + return ERROR_INSTALL_FAILURE;
199 + }
200 + szWorkingDir = szTempDir;
201 + fDeleteTemp = true;
202 + }
203 +
204 + wchar_t szConfigFilePath[MAX_PATH + 20];
205 + StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir);
206 + StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config");
207 +
208 + const wchar_t* szConfigFile = szConfigFilePath;
209 + if (!::PathFileExists(szConfigFilePath))
210 + {
211 + szConfigFile = NULL;
212 + }
213 +
214 + wchar_t szWIAssembly[MAX_PATH + 50];
215 + StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir);
216 + StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
217 +
218 + int iResult = ERROR_INSTALL_FAILURE;
219 + ICorRuntimeHost* pHost;
220 + if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost))
221 + {
222 + _AppDomain* pAppDomain;
223 + if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir,
224 + szConfigFile, &pAppDomain))
225 + {
226 + if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult))
227 + {
228 + iResult = ERROR_INSTALL_FAILURE;
229 + }
230 + HRESULT hr = pHost->UnloadDomain(pAppDomain);
231 + if (FAILED(hr))
232 + {
233 + Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr);
234 + }
235 + pAppDomain->Release();
236 + }
237 +
238 + pHost->Stop();
239 + pHost->Release();
240 + }
241 +
242 + if (fDeleteTemp)
243 + {
244 + DeleteDirectory(szTempDir);
245 + }
246 + return iResult;
247 +}
248 +
249 +/// <summary>
250 +/// Called by the system when the DLL is loaded.
251 +/// Saves the module handle for later use.
252 +/// </summary>
253 +BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved)
254 +{
255 + UNREFERENCED_PARAMETER(pReserved);
256 +
257 + switch (dwReason)
258 + {
259 + case DLL_PROCESS_ATTACH:
260 + g_hModule = hModule;
261 + break;
262 + case DLL_THREAD_ATTACH:
263 + case DLL_THREAD_DETACH:
264 + case DLL_PROCESS_DETACH:
265 + break;
266 + }
267 + return TRUE;
268 +}
269 +
270 +/// <summary>
271 +/// Loads and invokes the managed portion of the proxy.
272 +/// </summary>
273 +/// <param name="hSession">Handle to the installer session,
274 +/// used for logging errors and to be passed on to the custom action.</param>
275 +/// <param name="pAppDomain">AppDomain which has its application
276 +/// base set to the CA working directory.</param>
277 +/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
278 +/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
279 +/// string, or a simple name that maps to a full entrypoint definition
280 +/// in CustomAction.config.</param>
281 +/// <param name="piResult">Return value of the invoked custom
282 +/// action method.</param>
283 +/// <returns>True if the managed proxy was invoked successfully,
284 +/// false if there was some error. Note the custom action itself may
285 +/// return an error via piResult while this method still returns true
286 +/// since the invocation was successful.</returns>
287 +bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain,
288 + const wchar_t* szEntryPoint, int* piResult)
289 +{
290 + VARIANT vResult;
291 + ::VariantInit(&vResult);
292 +
293 + const bool f64bit = (sizeof(void*) == sizeof(LONGLONG));
294 + const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
295 + const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy";
296 + const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32");
297 +
298 + _MethodInfo* pCAInvokeMethod;
299 + if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName,
300 + szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod))
301 + {
302 + return false;
303 + }
304 +
305 + HRESULT hr;
306 + VARIANT vNull;
307 + vNull.vt = VT_EMPTY;
308 + SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
309 + VARIANT vSessionHandle;
310 + vSessionHandle.vt = VT_I4;
311 + vSessionHandle.intVal = hSession;
312 + LONG index = 0;
313 + hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
314 + if (FAILED(hr)) goto LExit;
315 + VARIANT vEntryPoint;
316 + vEntryPoint.vt = VT_BSTR;
317 + vEntryPoint.bstrVal = SysAllocString(szEntryPoint);
318 + if (vEntryPoint.bstrVal == NULL)
319 + {
320 + hr = E_OUTOFMEMORY;
321 + goto LExit;
322 + }
323 + index = 1;
324 + hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
325 + if (FAILED(hr)) goto LExit;
326 + VARIANT vRemotingFunctionPtr;
327 +#pragma warning(push)
328 +#pragma warning(disable:4127) // conditional expression is constant
329 + if (f64bit)
330 +#pragma warning(pop)
331 + {
332 + vRemotingFunctionPtr.vt = VT_I8;
333 + vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
334 + }
335 + else
336 + {
337 + vRemotingFunctionPtr.vt = VT_I4;
338 +#pragma warning(push)
339 +#pragma warning(disable:4302) // truncation
340 +#pragma warning(disable:4311) // pointer truncation
341 + vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
342 +#pragma warning(pop)
343 + }
344 + index = 2;
345 + hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr);
346 + if (FAILED(hr)) goto LExit;
347 +
348 + hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult);
349 +
350 +LExit:
351 + SafeArrayDestroy(saArgs);
352 + pCAInvokeMethod->Release();
353 +
354 + if (FAILED(hr))
355 + {
356 + Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr);
357 + return false;
358 + }
359 +
360 + *piResult = vResult.intVal;
361 + return true;
362 +}
363 +
src/samples/Dtf/Tools/SfxCA/SfxCA.rc new
+10
@@ -0,0 +1,10 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#define VER_DLL
4 +#define VER_LANG_NEUTRAL
5 +#define VER_ORIGINAL_FILENAME "SfxCA.dll"
6 +#define VER_INTERNAL_NAME "SfxCA"
7 +#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action"
8 +
9 +// Additional resources here
10 +
src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj new
+68
@@ -0,0 +1,68 @@
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 +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 + <ItemGroup Label="ProjectConfigurations">
5 + <ProjectConfiguration Include="Debug|Win32">
6 + <Configuration>Debug</Configuration>
7 + <Platform>Win32</Platform>
8 + </ProjectConfiguration>
9 + <ProjectConfiguration Include="Release|Win32">
10 + <Configuration>Release</Configuration>
11 + <Platform>Win32</Platform>
12 + </ProjectConfiguration>
13 + <ProjectConfiguration Include="Debug|x64">
14 + <Configuration>Debug</Configuration>
15 + <Platform>x64</Platform>
16 + </ProjectConfiguration>
17 + <ProjectConfiguration Include="Release|x64">
18 + <Configuration>Release</Configuration>
19 + <Platform>x64</Platform>
20 + </ProjectConfiguration>
21 + </ItemGroup>
22 +
23 + <PropertyGroup Label="Globals">
24 + <ProjectGuid>{55D5BA28-D427-4F53-80C2-FE9EF23C1553}</ProjectGuid>
25 + <ConfigurationType>DynamicLibrary</ConfigurationType>
26 + <TargetName>SfxCA</TargetName>
27 + <PlatformToolset>v142</PlatformToolset>
28 + <CharacterSet>Unicode</CharacterSet>
29 + <ProjectModuleDefinitionFile>EntryPoints.def</ProjectModuleDefinitionFile>
30 + </PropertyGroup>
31 + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
32 + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
33 + <PropertyGroup>
34 + <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
35 + </PropertyGroup>
36 + <ItemGroup>
37 + <ClCompile Include="ClrHost.cpp" />
38 + <ClCompile Include="Extract.cpp" />
39 + <ClCompile Include="precomp.cpp">
40 + <PrecompiledHeader>Create</PrecompiledHeader>
41 + </ClCompile>
42 + <ClCompile Include="RemoteMsi.cpp" />
43 + <ClCompile Include="SfxCA.cpp" />
44 + <ClCompile Include="SfxUtil.cpp" />
45 + <ClCompile Include="EmbeddedUI.cpp" />
46 + </ItemGroup>
47 + <ItemGroup>
48 + <ClInclude Include="precomp.h" />
49 + <ClInclude Include="EntryPoints.h" />
50 + <ClInclude Include="RemoteMsiSession.h" />
51 + <ClInclude Include="SfxUtil.h" />
52 + </ItemGroup>
53 + <ItemGroup>
54 + <None Include="EntryPoints.def" />
55 + <None Include="packages.config" />
56 + </ItemGroup>
57 + <ItemGroup>
58 + <ResourceCompile Include="SfxCA.rc" />
59 + </ItemGroup>
60 + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
61 + <Import Project="..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
62 + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
63 + <PropertyGroup>
64 + <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
65 + </PropertyGroup>
66 + <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
67 + </Target>
68 +</Project>
\ No newline at end of file
src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters new
+62
@@ -0,0 +1,62 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 + <ItemGroup>
4 + <ClCompile Include="ClrHost.cpp">
5 + <Filter>Source Files</Filter>
6 + </ClCompile>
7 + <ClCompile Include="EmbeddedUI.cpp">
8 + <Filter>Source Files</Filter>
9 + </ClCompile>
10 + <ClCompile Include="Extract.cpp">
11 + <Filter>Source Files</Filter>
12 + </ClCompile>
13 + <ClCompile Include="RemoteMsi.cpp">
14 + <Filter>Source Files</Filter>
15 + </ClCompile>
16 + <ClCompile Include="SfxCA.cpp">
17 + <Filter>Source Files</Filter>
18 + </ClCompile>
19 + <ClCompile Include="SfxUtil.cpp">
20 + <Filter>Source Files</Filter>
21 + </ClCompile>
22 + <ClCompile Include="precomp.cpp">
23 + <Filter>Source Files</Filter>
24 + </ClCompile>
25 + </ItemGroup>
26 + <ItemGroup>
27 + <ClInclude Include="EntryPoints.h">
28 + <Filter>Header Files</Filter>
29 + </ClInclude>
30 + <ClInclude Include="precomp.h">
31 + <Filter>Header Files</Filter>
32 + </ClInclude>
33 + <ClInclude Include="RemoteMsiSession.h">
34 + <Filter>Header Files</Filter>
35 + </ClInclude>
36 + <ClInclude Include="SfxUtil.h">
37 + <Filter>Header Files</Filter>
38 + </ClInclude>
39 + </ItemGroup>
40 + <ItemGroup>
41 + <Filter Include="Resource Files">
42 + <UniqueIdentifier>{81c92f68-18c2-4cd4-a588-5c3616860dd9}</UniqueIdentifier>
43 + </Filter>
44 + <Filter Include="Header Files">
45 + <UniqueIdentifier>{6cdc30ee-e14d-4679-b92e-3e080535e53b}</UniqueIdentifier>
46 + </Filter>
47 + <Filter Include="Source Files">
48 + <UniqueIdentifier>{1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d}</UniqueIdentifier>
49 + </Filter>
50 + </ItemGroup>
51 + <ItemGroup>
52 + <ResourceCompile Include="SfxCA.rc">
53 + <Filter>Resource Files</Filter>
54 + </ResourceCompile>
55 + </ItemGroup>
56 + <ItemGroup>
57 + <None Include="EntryPoints.def">
58 + <Filter>Resource Files</Filter>
59 + </None>
60 + <None Include="packages.config" />
61 + </ItemGroup>
62 +</Project>
\ No newline at end of file
src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp new
+209
@@ -0,0 +1,209 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
4 +#include "SfxUtil.h"
5 +
6 +/// <summary>
7 +/// Writes a formatted message to the MSI log.
8 +/// Does out-of-proc MSI calls if necessary.
9 +/// </summary>
10 +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...)
11 +{
12 + const int LOG_BUFSIZE = 4096;
13 + wchar_t szBuf[LOG_BUFSIZE];
14 + va_list args;
15 + va_start(args, szMessage);
16 + StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args);
17 +
18 + if (!g_fRunningOutOfProc || NULL == g_pRemote)
19 + {
20 + MSIHANDLE hRec = MsiCreateRecord(1);
21 + MsiRecordSetString(hRec, 0, L"SFXCA: [1]");
22 + MsiRecordSetString(hRec, 1, szBuf);
23 + MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec);
24 + MsiCloseHandle(hRec);
25 + }
26 + else
27 + {
28 + // Logging is the only remote-MSI operation done from unmanaged code.
29 + // It's not very convenient here because part of the infrastructure
30 + // for remote MSI APIs is on the managed side.
31 +
32 + RemoteMsiSession::RequestData req;
33 + RemoteMsiSession::RequestData* pResp = NULL;
34 + SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData));
35 +
36 + req.fields[0].vt = VT_UI4;
37 + req.fields[0].uiValue = 1;
38 + g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp);
39 + MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue;
40 +
41 + req.fields[0].vt = VT_I4;
42 + req.fields[0].iValue = (int) hRec;
43 + req.fields[1].vt = VT_UI4;
44 + req.fields[1].uiValue = 0;
45 + req.fields[2].vt = VT_LPWSTR;
46 + req.fields[2].szValue = L"SFXCA: [1]";
47 + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
48 +
49 + req.fields[0].vt = VT_I4;
50 + req.fields[0].iValue = (int) hRec;
51 + req.fields[1].vt = VT_UI4;
52 + req.fields[1].uiValue = 1;
53 + req.fields[2].vt = VT_LPWSTR;
54 + req.fields[2].szValue = szBuf;
55 + g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
56 +
57 + req.fields[0].vt = VT_I4;
58 + req.fields[0].iValue = (int) hSession;
59 + req.fields[1].vt = VT_I4;
60 + req.fields[1].iValue = (int) INSTALLMESSAGE_INFO;
61 + req.fields[2].vt = VT_I4;
62 + req.fields[2].iValue = (int) hRec;
63 + g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp);
64 +
65 + req.fields[0].vt = VT_I4;
66 + req.fields[0].iValue = (int) hRec;
67 + req.fields[1].vt = VT_EMPTY;
68 + req.fields[2].vt = VT_EMPTY;
69 + g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp);
70 + }
71 +}
72 +
73 +/// <summary>
74 +/// Deletes a directory, including all files and subdirectories.
75 +/// </summary>
76 +/// <param name="szDir">Path to the directory to delete,
77 +/// not including a trailing backslash.</param>
78 +/// <returns>True if the directory was successfully deleted, or false
79 +/// if the deletion failed (most likely because some files were locked).
80 +/// </returns>
81 +bool DeleteDirectory(const wchar_t* szDir)
82 +{
83 + size_t cchDir = wcslen(szDir);
84 + size_t cchPathBuf = cchDir + 3 + MAX_PATH;
85 + wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t));
86 + if (szPath == NULL) return false;
87 + StringCchCopy(szPath, cchPathBuf, szDir);
88 + StringCchCat(szPath, cchPathBuf, L"\\*");
89 + WIN32_FIND_DATA fd;
90 + HANDLE hSearch = FindFirstFile(szPath, &fd);
91 + while (hSearch != INVALID_HANDLE_VALUE)
92 + {
93 + StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName);
94 + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
95 + {
96 + if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0)
97 + {
98 + DeleteDirectory(szPath);
99 + }
100 + }
101 + else
102 + {
103 + DeleteFile(szPath);
104 + }
105 + if (!FindNextFile(hSearch, &fd))
106 + {
107 + FindClose(hSearch);
108 + hSearch = INVALID_HANDLE_VALUE;
109 + }
110 + }
111 + return RemoveDirectory(szDir) != 0;
112 +}
113 +
114 +bool DirectoryExists(const wchar_t* szDir)
115 +{
116 + if (szDir != NULL)
117 + {
118 + DWORD dwAttrs = GetFileAttributes(szDir);
119 + if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
120 + {
121 + return true;
122 + }
123 + }
124 + return false;
125 +}
126 +
127 +/// <summary>
128 +/// Extracts a cabinet that is concatenated to a module
129 +/// to a new temporary directory.
130 +/// </summary>
131 +/// <param name="hSession">Handle to the installer session,
132 +/// used just for logging.</param>
133 +/// <param name="hModule">Module that has the concatenated cabinet.</param>
134 +/// <param name="szTempDir">Buffer for returning the path of the
135 +/// created temp directory.</param>
136 +/// <param name="cchTempDirBuf">Size in characters of the buffer.
137 +/// <returns>True if the files were extracted, or false if the
138 +/// buffer was too small or the directory could not be created
139 +/// or the extraction failed for some other reason.</returns>
140 +__success(return != false)
141 +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
142 + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
143 +{
144 + wchar_t szModule[MAX_PATH];
145 + DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
146 + if (cchCopied == 0)
147 + {
148 + Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
149 + return false;
150 + }
151 + else if (cchCopied == MAX_PATH - 1)
152 + {
153 + Log(hSession, L"Failed to get module path -- path is too long.");
154 + return false;
155 + }
156 +
157 + if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
158 + {
159 + Log(hSession, L"Temp directory buffer is NULL or too small.");
160 + return false;
161 + }
162 + StringCchCopy(szTempDir, cchTempDirBuf, szModule);
163 + StringCchCat(szTempDir, cchTempDirBuf, L"-");
164 +
165 + DWORD cchTempDir = (DWORD) wcslen(szTempDir);
166 + for (int i = 0; DirectoryExists(szTempDir); i++)
167 + {
168 + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
169 + }
170 +
171 + if (!CreateDirectory(szTempDir, NULL))
172 + {
173 + cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
174 + if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
175 + {
176 + Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
177 + return false;
178 + }
179 +
180 + wchar_t* szModuleName = wcsrchr(szModule, L'\\');
181 + if (szModuleName == NULL) szModuleName = szModule;
182 + else szModuleName = szModuleName + 1;
183 + StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
184 + StringCchCat(szTempDir, cchTempDirBuf, L"-");
185 +
186 + cchTempDir = (DWORD) wcslen(szTempDir);
187 + for (int i = 0; DirectoryExists(szTempDir); i++)
188 + {
189 + swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
190 + }
191 +
192 + if (!CreateDirectory(szTempDir, NULL))
193 + {
194 + Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
195 + return false;
196 + }
197 + }
198 +
199 + Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);
200 + int err = ExtractCabinet(szModule, szTempDir);
201 + if (err != 0)
202 + {
203 + Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
204 + DeleteDirectory(szTempDir);
205 + return false;
206 + }
207 + return true;
208 +}
209 +
src/samples/Dtf/Tools/SfxCA/SfxUtil.h new
+31
@@ -0,0 +1,31 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "RemoteMsiSession.h"
4 +
5 +void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6 +
7 +int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir);
8 +
9 +bool DeleteDirectory(const wchar_t* szDir);
10 +
11 +__success(return != false)
12 +bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
13 + __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf);
14 +
15 +bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
16 + const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost);
17 +
18 +bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
19 + const wchar_t* szName, const wchar_t* szAppBase,
20 + const wchar_t* szConfigFile, _AppDomain** ppAppDomain);
21 +
22 +bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
23 + const wchar_t* szAssembly, const wchar_t* szClass,
24 + const wchar_t* szMethod, _MethodInfo** ppCAMethod);
25 +
26 +extern HMODULE g_hModule;
27 +extern bool g_fRunningOutOfProc;
28 +
29 +extern RemoteMsiSession* g_pRemote;
30 +
31 +
src/samples/Dtf/Tools/SfxCA/packages.config new
+4
@@ -0,0 +1,4 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<packages>
3 + <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
4 +</packages>
\ No newline at end of file
src/samples/Dtf/Tools/SfxCA/precomp.cpp new
+3
@@ -0,0 +1,3 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +#include "precomp.h"
\ No newline at end of file
src/samples/Dtf/Tools/SfxCA/precomp.h new
+18
@@ -0,0 +1,18 @@
1 +#pragma once
2 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3 +
4 +
5 +#include <windows.h>
6 +#include <msiquery.h>
7 +#include <strsafe.h>
8 +#include <mscoree.h>
9 +#include <io.h>
10 +#include <fcntl.h>
11 +#include <share.h>
12 +#include <shlwapi.h>
13 +#include <sys/stat.h>
14 +#include <malloc.h>
15 +#include <fdi.h>
16 +#include <msiquery.h>
17 +#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent", "CorReportEvent")
18 +using namespace mscorlib;
src/samples/Dtf/Tools/Tools.proj new
+15
@@ -0,0 +1,15 @@
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 +
5 +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 + <ItemGroup>
7 + <ProjectReference Include="MakeSfxCA\MakeSfxCA.csproj" />
8 + <ProjectReference Include="SfxCA\SfxCA.vcxproj" />
9 + <ProjectReference Include="SfxCA\SfxCA.vcxproj">
10 + <Properties>Platform=x64</Properties>
11 + </ProjectReference>
12 + </ItemGroup>
13 +
14 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\Traversal.targets" />
15 +</Project>
src/samples/Dtf/WiFile/WiFile.cs new
+147
@@ -0,0 +1,147 @@
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 +using System;
4 +using System.IO;
5 +using System.Reflection;
6 +using System.Collections;
7 +using System.Collections.Generic;
8 +using System.Diagnostics.CodeAnalysis;
9 +using System.Globalization;
10 +using System.Text.RegularExpressions;
11 +using WixToolset.Dtf.WindowsInstaller;
12 +using WixToolset.Dtf.WindowsInstaller.Package;
13 +
14 +[assembly: AssemblyDescription("Windows Installer package file extraction and update tool")]
15 +
16 +
17 +/// <summary>
18 +/// Shows sample use of the InstallPackage class.
19 +/// </summary>
20 +public class WiFile
21 +{
22 + public static void Usage(TextWriter w)
23 + {
24 + w.WriteLine("Usage: WiFile.exe package.msi /l [filename,filename2,...]");
25 + w.WriteLine("Usage: WiFile.exe package.msi /x [filename,filename2,...]");
26 + w.WriteLine("Usage: WiFile.exe package.msi /u [filename,filename2,...]");
27 + w.WriteLine();
28 + w.WriteLine("Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM.");
29 + w.WriteLine("Files are extracted using their source path relative to the package.");
30 + w.WriteLine("Specified filenames do not include paths.");
31 + w.WriteLine("Filenames may be a pattern such as *.exe or file?.dll");
32 + }
33 +
34 + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
35 + public static int Main(string[] args)
36 + {
37 + if(!(args.Length == 2 || args.Length == 3))
38 + {
39 + Usage(Console.Out);
40 + return -1;
41 + }
42 +
43 + string msiFile = args[0];
44 +
45 + string option = args[1].ToLowerInvariant();
46 + if(option.StartsWith("-", StringComparison.Ordinal)) option = "/" + option.Substring(1);
47 +
48 + string[] fileNames = null;
49 + if(args.Length == 3)
50 + {
51 + fileNames = args[2].Split(',');
52 + }
53 +
54 + try
55 + {
56 + switch(option)
57 + {
58 + case "/l":
59 + using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly))
60 + {
61 + pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
62 + IEnumerable<string> fileKeys = (fileNames != null ? FindFileKeys(pkg, fileNames) : pkg.Files.Keys);
63 +
64 + foreach(string fileKey in fileKeys)
65 + {
66 + Console.WriteLine(pkg.Files[fileKey]);
67 + }
68 + }
69 + break;
70 +
71 + case "/x":
72 + using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly))
73 + {
74 + pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
75 + ICollection<string> fileKeys = FindFileKeys(pkg, fileNames);
76 +
77 + pkg.ExtractFiles(fileKeys);
78 + }
79 + break;
80 +
81 + case "/u":
82 + using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.Transact))
83 + {
84 + pkg.Message += new InstallPackageMessageHandler(Console.WriteLine);
85 + ICollection<string> fileKeys = FindFileKeys(pkg, fileNames);
86 +
87 + pkg.UpdateFiles(fileKeys);
88 + pkg.Commit();
89 + }
90 + break;
91 +
92 + default:
93 + Usage(Console.Out);
94 + return -1;
95 + }
96 + }
97 + catch(InstallerException iex)
98 + {
99 + Console.WriteLine("Error: " + iex.Message);
100 + return iex.ErrorCode != 0 ? iex.ErrorCode : 1;
101 + }
102 + catch(FileNotFoundException fnfex)
103 + {
104 + Console.WriteLine(fnfex.Message);
105 + return 2;
106 + }
107 + catch(Exception ex)
108 + {
109 + Console.WriteLine("Error: " + ex.Message);
110 + return 1;
111 + }
112 + return 0;
113 + }
114 +
115 + static ICollection<string> FindFileKeys(InstallPackage pkg, ICollection<string> fileNames)
116 + {
117 + List<string> fileKeys = null;
118 + if(fileNames != null)
119 + {
120 + fileKeys = new List<string>();
121 + foreach(string fileName in fileNames)
122 + {
123 + string[] foundFileKeys = null;
124 + if(fileName.IndexOfAny(new char[] { '*', '?' }) >= 0)
125 + {
126 + foundFileKeys = pkg.FindFiles(FilePatternToRegex(fileName));
127 + }
128 + else
129 + {
130 + foundFileKeys = pkg.FindFiles(fileName);
131 + }
132 + fileKeys.AddRange(foundFileKeys);
133 + }
134 + if(fileKeys.Count == 0)
135 + {
136 + throw new FileNotFoundException("Files not found in package.");
137 + }
138 + }
139 + return fileKeys;
140 + }
141 +
142 + static Regex FilePatternToRegex(string pattern)
143 + {
144 + return new Regex("^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$",
145 + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
146 + }
147 +}
src/samples/Dtf/WiFile/WiFile.csproj new
+27
@@ -0,0 +1,27 @@
1 +
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 + <PropertyGroup>
7 + <ProjectGuid>{AE562F7F-EE33-41D6-A962-DA488FEFBD08}</ProjectGuid>
8 + <OutputType>Exe</OutputType>
9 + <RootNamespace>WixToolset.Dtf.Samples.WiFile</RootNamespace>
10 + <AssemblyName>WiFile</AssemblyName>
11 + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="WiFile.cs" />
16 + </ItemGroup>
17 +
18 + <ItemGroup>
19 + <Reference Include="System" />
20 + <Reference Include="System.Data" />
21 + <Reference Include="System.Xml" />
22 + <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
23 + <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
24 + </ItemGroup>
25 +
26 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27 +</Project>
src/samples/Dtf/XPack/AssemblyInfo.cs new
+5
@@ -0,0 +1,5 @@
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 +using System.Reflection;
4 +
5 +[assembly: AssemblyDescription("Simple command-line CAB/ZIP packing and unpacking tool.")]
src/samples/Dtf/XPack/XPack.cs new
+80
@@ -0,0 +1,80 @@
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 +namespace WixToolset.Dtf.Samples.XPack
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Collections.Generic;
8 + using System.Text;
9 + using WixToolset.Dtf.Compression;
10 +
11 + public class XPack
12 + {
13 + public static void Usage(TextWriter writer)
14 + {
15 + writer.WriteLine("Usage: XPack /P <archive.cab> <directory>");
16 + writer.WriteLine("Usage: XPack /P <archive.zip> <directory>");
17 + writer.WriteLine();
18 + writer.WriteLine("Packs all files in a directory tree into an archive,");
19 + writer.WriteLine("using either the cab or zip format. Any existing archive");
20 + writer.WriteLine("with the same name will be overwritten.");
21 + writer.WriteLine();
22 + writer.WriteLine("Usage: XPack /U <archive.cab> <directory>");
23 + writer.WriteLine("Usage: XPack /U <archive.zip> <directory>");
24 + writer.WriteLine();
25 + writer.WriteLine("Unpacks all files from a cab or zip archive to the");
26 + writer.WriteLine("specified directory. Any existing files with the same");
27 + writer.WriteLine("names will be overwritten.");
28 + }
29 +
30 + public static void Main(string[] args)
31 + {
32 + try
33 + {
34 + if (args.Length == 3 && args[0].ToUpperInvariant() == "/P")
35 + {
36 + ArchiveInfo a = GetArchive(args[1]);
37 + a.Pack(args[2], true, CompressionLevel.Max, ProgressHandler);
38 + }
39 + else if (args.Length == 3 && args[0].ToUpperInvariant() == "/U")
40 + {
41 + ArchiveInfo a = GetArchive(args[1]);
42 + a.Unpack(args[2], ProgressHandler);
43 + }
44 + else
45 + {
46 + Usage(Console.Out);
47 + }
48 + }
49 + catch (Exception ex)
50 + {
51 + Console.WriteLine(ex);
52 + }
53 + }
54 +
55 + private static void ProgressHandler(object source, ArchiveProgressEventArgs e)
56 + {
57 + if (e.ProgressType == ArchiveProgressType.StartFile)
58 + {
59 + Console.WriteLine(e.CurrentFileName);
60 + }
61 + }
62 +
63 + private static ArchiveInfo GetArchive(string name)
64 + {
65 + string extension = Path.GetExtension(name).ToUpperInvariant();
66 + if (extension == ".CAB")
67 + {
68 + return new WixToolset.Dtf.Compression.Cab.CabInfo(name);
69 + }
70 + else if (extension == ".ZIP")
71 + {
72 + return new WixToolset.Dtf.Compression.Zip.ZipInfo(name);
73 + }
74 + else
75 + {
76 + throw new ArgumentException("Unknown archive file extension: " + extension);
77 + }
78 + }
79 + }
80 +}
src/samples/Dtf/XPack/XPack.csproj new
+27
@@ -0,0 +1,27 @@
1 +
2 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 +
4 +
5 +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6 + <PropertyGroup>
7 + <ProjectGuid>{03E55D95-DABE-4571-9CDA-92A44F92A465}</ProjectGuid>
8 + <OutputType>Exe</OutputType>
9 + <RootNamespace>WixToolset.Dtf.Samples.XPack</RootNamespace>
10 + <AssemblyName>XPack</AssemblyName>
11 + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 + </PropertyGroup>
13 +
14 + <ItemGroup>
15 + <Compile Include="AssemblyInfo.cs" />
16 + <Compile Include="XPack.cs" />
17 + </ItemGroup>
18 +
19 + <ItemGroup>
20 + <Reference Include="System" />
21 + <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
22 + <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
23 + <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
24 + </ItemGroup>
25 +
26 + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27 +</Project>
src/version.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "version": "4.0.0-preview.0-build.{height}",
3 + "publicReleaseRefSpec": [
4 + "^refs/heads/master$"
5 + ],
6 + "cloudBuild": {
7 + "buildNumber": {
8 + "enabled": true
9 + }
10 + }
11 +}
src/wix.snk
Binary files /dev/null and b/src/wix.snk differ