aboutsummaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-07-14 15:19:53 -0700
committerRob Mensching <rob@firegiant.com>2022-07-14 16:02:24 -0700
commit229242cf7c328b89b5aa65ed7a04e33c8b93b393 (patch)
treede0a9547e73e46490b0946d6850228d5b30258b8 /src/tools
parentf46ca6a9dce91607ffc9855270dd6998216e1a8b (diff)
downloadwix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.gz
wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.tar.bz2
wix-229242cf7c328b89b5aa65ed7a04e33c8b93b393.zip
Rename "samples" segment to "tools"
This segment is a bit of a "miscellaneous section" in the WiX repo. As such it has been difficult to name. I originally eschewed the name "tools" because what is in the "wix" segment was once called "tools". However, now that wix.exe is firmly established as the entry point for WiX operations, I've become comfortable with its segment being named "wix". That meant "tools" was again available and "tools" better describes the content of this section.
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/Directory.Build.props10
-rw-r--r--src/tools/Dtf/DDiff/CabDiffEngine.cs131
-rw-r--r--src/tools/Dtf/DDiff/DDiff.cs72
-rw-r--r--src/tools/Dtf/DDiff/DDiff.csproj39
-rw-r--r--src/tools/Dtf/DDiff/DirectoryDiffEngine.cs154
-rw-r--r--src/tools/Dtf/DDiff/FileDiffEngine.cs83
-rw-r--r--src/tools/Dtf/DDiff/IDiffEngine.cs68
-rw-r--r--src/tools/Dtf/DDiff/MsiDiffEngine.cs276
-rw-r--r--src/tools/Dtf/DDiff/MspDiffEngine.cs127
-rw-r--r--src/tools/Dtf/DDiff/TextFileDiffEngine.cs83
-rw-r--r--src/tools/Dtf/DDiff/VersionedFileDiffEngine.cs90
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/about.htm59
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/buildingcas.htm94
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/cabpack.htm63
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/cabs.htm101
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/cabwrapper.htm63
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/caconfig.htm83
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/caproxy.htm74
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/databases.htm120
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/debuggingcas.htm66
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/dependencies.htm88
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm34
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/history.htm437
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/installutil.htm94
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/inventory.htm78
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/managedcas.htm53
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/msihelper.htm59
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/msiwrapper.htm80
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/packages.htm86
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/powerdiff.htm71
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/samplecas.htm84
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/samples.htm59
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/support.htm52
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/using.htm50
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/whatsnew.htm257
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/wifile.htm73
-rw-r--r--src/tools/Dtf/Documents/Guide/Content/writingcas.htm114
-rw-r--r--src/tools/Dtf/Documents/Guide/DTF.hhc132
-rw-r--r--src/tools/Dtf/Documents/Guide/DTF.hhk126
-rw-r--r--src/tools/Dtf/Documents/Guide/DTF.hhp49
-rw-r--r--src/tools/Dtf/Documents/Guide/dtfguide.helpproj29
-rw-r--r--src/tools/Dtf/Documents/Guide/styles/presentation.css394
-rw-r--r--src/tools/Dtf/Documents/Reference/Compression.htm13
-rw-r--r--src/tools/Dtf/Documents/Reference/Compression1.pngbin0 -> 79032 bytes
-rw-r--r--src/tools/Dtf/Documents/Reference/Compression2.pngbin0 -> 68312 bytes
-rw-r--r--src/tools/Dtf/Documents/Reference/WindowsInstaller.htm14
-rw-r--r--src/tools/Dtf/Documents/Reference/WindowsInstaller1.pngbin0 -> 207803 bytes
-rw-r--r--src/tools/Dtf/Documents/Reference/WindowsInstaller2.pngbin0 -> 180714 bytes
-rw-r--r--src/tools/Dtf/Documents/Reference/WindowsInstaller3.pngbin0 -> 120423 bytes
-rw-r--r--src/tools/Dtf/Documents/Reference/dtfref.shfbproj75
-rw-r--r--src/tools/Dtf/Documents/Reference/helplink.js184
-rw-r--r--src/tools/Dtf/Inventory/Columns.resx252
-rw-r--r--src/tools/Dtf/Inventory/Features.cs107
-rw-r--r--src/tools/Dtf/Inventory/IInventoryDataProvider.cs67
-rw-r--r--src/tools/Dtf/Inventory/Inventory.cs1231
-rw-r--r--src/tools/Dtf/Inventory/Inventory.csproj42
-rw-r--r--src/tools/Dtf/Inventory/Inventory.icobin0 -> 4710 bytes
-rw-r--r--src/tools/Dtf/Inventory/Inventory.resx265
-rw-r--r--src/tools/Dtf/Inventory/components.cs626
-rw-r--r--src/tools/Dtf/Inventory/msiutils.cs46
-rw-r--r--src/tools/Dtf/Inventory/patches.cs227
-rw-r--r--src/tools/Dtf/Inventory/products.cs145
-rw-r--r--src/tools/Dtf/Inventory/xp.manifest15
-rw-r--r--src/tools/Dtf/WiFile/WiFile.cs147
-rw-r--r--src/tools/Dtf/WiFile/WiFile.csproj27
-rw-r--r--src/tools/Dtf/XPack/AssemblyInfo.cs5
-rw-r--r--src/tools/Dtf/XPack/XPack.cs80
-rw-r--r--src/tools/Dtf/XPack/XPack.csproj27
-rw-r--r--src/tools/ThmViewerPackage/Package.wxs30
-rw-r--r--src/tools/ThmViewerPackage/ThmViewerPackage.wixproj10
-rw-r--r--src/tools/WixToolset.Templates/WixToolset.Templates.csproj20
-rw-r--r--src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json14
-rw-r--r--src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs4
-rw-r--r--src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj5
-rw-r--r--src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json14
-rw-r--r--src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs4
-rw-r--r--src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj5
-rw-r--r--src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json26
-rw-r--r--src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs8
-rw-r--r--src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj2
-rw-r--r--src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json26
-rw-r--r--src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs24
-rw-r--r--src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs33
-rw-r--r--src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs23
-rw-r--r--src/tools/burn/ManagedBundleRunner/BundleResult.cs24
-rw-r--r--src/tools/burn/ManagedBundleRunner/BundleRunner.cs212
-rw-r--r--src/tools/burn/runbundle/AssemblyInfo.cs12
-rw-r--r--src/tools/burn/runbundle/Program.cs47
-rw-r--r--src/tools/thmviewer/Resources/LoremIpsum.rtfbin0 -> 4870 bytes
-rw-r--r--src/tools/thmviewer/Resources/thm.xml11
-rw-r--r--src/tools/thmviewer/display.cpp339
-rw-r--r--src/tools/thmviewer/load.cpp221
-rw-r--r--src/tools/thmviewer/precomp.cpp3
-rw-r--r--src/tools/thmviewer/precomp.h72
-rw-r--r--src/tools/thmviewer/resource.h16
-rw-r--r--src/tools/thmviewer/thmviewer.cpp564
-rw-r--r--src/tools/thmviewer/thmviewer.manifest19
-rw-r--r--src/tools/thmviewer/thmviewer.rc12
-rw-r--r--src/tools/thmviewer/thmviewer.v3.ncrunchproject7
-rw-r--r--src/tools/thmviewer/thmviewer.vcxproj69
-rw-r--r--src/tools/thmviewer/thmviewer.vcxproj.filters56
-rw-r--r--src/tools/tools.cmd18
-rw-r--r--src/tools/tools.sln40
103 files changed, 9940 insertions, 0 deletions
diff --git a/src/tools/Directory.Build.props b/src/tools/Directory.Build.props
new file mode 100644
index 00000000..3fdc6553
--- /dev/null
+++ b/src/tools/Directory.Build.props
@@ -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<Project>
5 <PropertyGroup>
6 <SegmentName>tools</SegmentName>
7 </PropertyGroup>
8
9 <Import Project="..\Directory.Build.props" />
10</Project>
diff --git a/src/tools/Dtf/DDiff/CabDiffEngine.cs b/src/tools/Dtf/DDiff/CabDiffEngine.cs
new file mode 100644
index 00000000..b3bafaa3
--- /dev/null
+++ b/src/tools/Dtf/DDiff/CabDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5using System.Collections;
6using System.Collections.Generic;
7using WixToolset.Dtf.Compression.Cab;
8
9namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/DDiff.cs b/src/tools/Dtf/DDiff/DDiff.cs
new file mode 100644
index 00000000..4e9121b6
--- /dev/null
+++ b/src/tools/Dtf/DDiff/DDiff.cs
@@ -0,0 +1,72 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Text;
6
7namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/DDiff.csproj b/src/tools/Dtf/DDiff/DDiff.csproj
new file mode 100644
index 00000000..bf4d6521
--- /dev/null
+++ b/src/tools/Dtf/DDiff/DDiff.csproj
@@ -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.Tools.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>
diff --git a/src/tools/Dtf/DDiff/DirectoryDiffEngine.cs b/src/tools/Dtf/DDiff/DirectoryDiffEngine.cs
new file mode 100644
index 00000000..a5216f22
--- /dev/null
+++ b/src/tools/Dtf/DDiff/DirectoryDiffEngine.cs
@@ -0,0 +1,154 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using System.Collections;
6
7namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/FileDiffEngine.cs b/src/tools/Dtf/DDiff/FileDiffEngine.cs
new file mode 100644
index 00000000..9eb197ea
--- /dev/null
+++ b/src/tools/Dtf/DDiff/FileDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5
6namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/IDiffEngine.cs b/src/tools/Dtf/DDiff/IDiffEngine.cs
new file mode 100644
index 00000000..918e42e6
--- /dev/null
+++ b/src/tools/Dtf/DDiff/IDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5using System.Collections;
6
7namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/MsiDiffEngine.cs b/src/tools/Dtf/DDiff/MsiDiffEngine.cs
new file mode 100644
index 00000000..4e3b2c7b
--- /dev/null
+++ b/src/tools/Dtf/DDiff/MsiDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5using System.Collections;
6using System.Collections.Generic;
7using WixToolset.Dtf.WindowsInstaller;
8using WixToolset.Dtf.WindowsInstaller.Package;
9
10namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/MspDiffEngine.cs b/src/tools/Dtf/DDiff/MspDiffEngine.cs
new file mode 100644
index 00000000..a32ab24a
--- /dev/null
+++ b/src/tools/Dtf/DDiff/MspDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5using WixToolset.Dtf.WindowsInstaller;
6using WixToolset.Dtf.WindowsInstaller.Package;
7
8namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/TextFileDiffEngine.cs b/src/tools/Dtf/DDiff/TextFileDiffEngine.cs
new file mode 100644
index 00000000..beb5ea84
--- /dev/null
+++ b/src/tools/Dtf/DDiff/TextFileDiffEngine.cs
@@ -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
3using System;
4using System.IO;
5using System.Diagnostics;
6
7namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/DDiff/VersionedFileDiffEngine.cs b/src/tools/Dtf/DDiff/VersionedFileDiffEngine.cs
new file mode 100644
index 00000000..9a3e399f
--- /dev/null
+++ b/src/tools/Dtf/DDiff/VersionedFileDiffEngine.cs
@@ -0,0 +1,90 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.IO;
5using WixToolset.Dtf.WindowsInstaller;
6
7namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Documents/Guide/Content/about.htm b/src/tools/Dtf/Documents/Guide/Content/about.htm
new file mode 100644
index 00000000..393b5a81
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/about.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/buildingcas.htm b/src/tools/Dtf/Documents/Guide/Content/buildingcas.htm
new file mode 100644
index 00000000..e88ad552
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/buildingcas.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/cabpack.htm b/src/tools/Dtf/Documents/Guide/Content/cabpack.htm
new file mode 100644
index 00000000..2d9f725e
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/cabpack.htm
@@ -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;
26Usage: XPack /P &lt;archive.cab&gt; &lt;directory&gt;
27Usage: XPack /P &lt;archive.zip&gt; &lt;directory&gt;
28
29Packs all files in a directory tree into an archive,
30using either the cab or zip format. Any existing archive
31with the same name will be overwritten.
32
33
34Usage: XPack /U &lt;archive.cab&gt; &lt;directory&gt;
35Usage: XPack /U &lt;archive.zip&gt; &lt;directory&gt;
36
37Unpacks all files from a cab or zip archive to the
38specified directory. Any existing files with the same
39names 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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/cabs.htm b/src/tools/Dtf/Documents/Guide/Content/cabs.htm
new file mode 100644
index 00000000..e88d1e15
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/cabs.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/cabwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/cabwrapper.htm
new file mode 100644
index 00000000..fd88437c
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/cabwrapper.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/caconfig.htm b/src/tools/Dtf/Documents/Guide/Content/caconfig.htm
new file mode 100644
index 00000000..a6c97d2b
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/caconfig.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/caproxy.htm b/src/tools/Dtf/Documents/Guide/Content/caproxy.htm
new file mode 100644
index 00000000..2ee962d5
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/caproxy.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/databases.htm b/src/tools/Dtf/Documents/Guide/Content/databases.htm
new file mode 100644
index 00000000..4fe1fba9
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/databases.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/debuggingcas.htm b/src/tools/Dtf/Documents/Guide/Content/debuggingcas.htm
new file mode 100644
index 00000000..ca1be161
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/debuggingcas.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/dependencies.htm b/src/tools/Dtf/Documents/Guide/Content/dependencies.htm
new file mode 100644
index 00000000..cfec5880
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/dependencies.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm
new file mode 100644
index 00000000..6bab69b5
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/filepatchwrapper.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/history.htm b/src/tools/Dtf/Documents/Guide/Content/history.htm
new file mode 100644
index 00000000..704ce875
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/history.htm
@@ -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
350Documentation!<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>
414Initial 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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/installutil.htm b/src/tools/Dtf/Documents/Guide/Content/installutil.htm
new file mode 100644
index 00000000..e235a7b6
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/installutil.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/inventory.htm b/src/tools/Dtf/Documents/Guide/Content/inventory.htm
new file mode 100644
index 00000000..40a6ef74
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/inventory.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/managedcas.htm b/src/tools/Dtf/Documents/Guide/Content/managedcas.htm
new file mode 100644
index 00000000..9cce0432
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/managedcas.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/msihelper.htm b/src/tools/Dtf/Documents/Guide/Content/msihelper.htm
new file mode 100644
index 00000000..c1493117
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/msihelper.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/msiwrapper.htm b/src/tools/Dtf/Documents/Guide/Content/msiwrapper.htm
new file mode 100644
index 00000000..70190ac4
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/msiwrapper.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/packages.htm b/src/tools/Dtf/Documents/Guide/Content/packages.htm
new file mode 100644
index 00000000..aa521685
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/packages.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/powerdiff.htm b/src/tools/Dtf/Documents/Guide/Content/powerdiff.htm
new file mode 100644
index 00000000..f420b47e
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/powerdiff.htm
@@ -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]
28Example: DDiff d:\dir1 d:\dir2
29Example: DDiff setup1.msi setup2.msi
30Example: DDiff patch1.msp patch2.msp -patchtarget target.msi
31Example: DDiff package1.cab package2.cab
32
33Options:
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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/samplecas.htm b/src/tools/Dtf/Documents/Guide/Content/samplecas.htm
new file mode 100644
index 00000000..4dfed6f0
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/samplecas.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/samples.htm b/src/tools/Dtf/Documents/Guide/Content/samples.htm
new file mode 100644
index 00000000..3bcd379a
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/samples.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/support.htm b/src/tools/Dtf/Documents/Guide/Content/support.htm
new file mode 100644
index 00000000..89acbadf
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/support.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/using.htm b/src/tools/Dtf/Documents/Guide/Content/using.htm
new file mode 100644
index 00000000..6fe960e8
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/using.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/whatsnew.htm b/src/tools/Dtf/Documents/Guide/Content/whatsnew.htm
new file mode 100644
index 00000000..3efe67bd
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/whatsnew.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/wifile.htm b/src/tools/Dtf/Documents/Guide/Content/wifile.htm
new file mode 100644
index 00000000..20998b73
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/wifile.htm
@@ -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,...]
26Usage: WiFile.exe package.msi /x [filename,filename2,...]
27Usage: WiFile.exe package.msi /u [filename,filename2,...]
28
29Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM.
30Files are extracted using their source path relative to the package.
31Specified filenames do not include paths.
32Filenames 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>
diff --git a/src/tools/Dtf/Documents/Guide/Content/writingcas.htm b/src/tools/Dtf/Documents/Guide/Content/writingcas.htm
new file mode 100644
index 00000000..6beccf5f
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/Content/writingcas.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/DTF.hhc b/src/tools/Dtf/Documents/Guide/DTF.hhc
new file mode 100644
index 00000000..bf43e447
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/DTF.hhc
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/DTF.hhk b/src/tools/Dtf/Documents/Guide/DTF.hhk
new file mode 100644
index 00000000..bc6e49b3
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/DTF.hhk
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/DTF.hhp b/src/tools/Dtf/Documents/Guide/DTF.hhp
new file mode 100644
index 00000000..e9b8ad90
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/DTF.hhp
@@ -0,0 +1,49 @@
1[OPTIONS]
2Auto Index=Yes
3Compatibility=1.1 or later
4Compiled file=DTF.chm
5Contents file=DTF.hhc
6Default Window=DTF
7Default topic=Content\about.htm
8Display compile progress=Yes
9Error log file=DTFhelp.log
10Full-text search=Yes
11Index file=DTF.hhk
12Language=0x409 English (United States)
13Title=Deployment Tools Foundation
14
15[WINDOWS]
16DTF="Deployment Tools Foundation","DTF.hhc","DTF.hhk","Content\about.htm","Content\about.htm",,,,,0x22520,,0x384e,[143,72,937,601],,,,,,,0
17
18
19[FILES]
20styles\presentation.css
21Content\about.htm
22Content\whatsnew.htm
23Content\dependencies.htm
24Content\using.htm
25DTF.hhc
26DTF.hhk
27Content\samples.htm
28Content\support.htm
29Content\history.htm
30Content\inventory.htm
31Content\wifile.htm
32Content\cabpack.htm
33Content\powerdiff.htm
34Content\cabs.htm
35Content\databases.htm
36Content\packages.htm
37Content\managedcas.htm
38Content\samplecas.htm
39Content\writingcas.htm
40Content\buildingcas.htm
41Content\debuggingcas.htm
42Content\caproxy.htm
43Content\caconfig.htm
44Content\installutil.htm
45
46[MERGE FILES]
47DTFAPI.chm
48
49[INFOTYPES]
diff --git a/src/tools/Dtf/Documents/Guide/dtfguide.helpproj b/src/tools/Dtf/Documents/Guide/dtfguide.helpproj
new file mode 100644
index 00000000..4df2765d
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/dtfguide.helpproj
@@ -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>
diff --git a/src/tools/Dtf/Documents/Guide/styles/presentation.css b/src/tools/Dtf/Documents/Guide/styles/presentation.css
new file mode 100644
index 00000000..b71c8582
--- /dev/null
+++ b/src/tools/Dtf/Documents/Guide/styles/presentation.css
@@ -0,0 +1,394 @@
1
2/* page style */
3
4body {
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
13table {
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
20div#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
30span.productTitle {
31 font-size: 80%;
32}
33
34span.topicTitle {
35 font-size: 140%;
36 font-weight: bold;
37 color: #003399;
38}
39
40span#chickenFeet {
41 float: left;
42}
43
44span#languageFilter {
45 float: right;
46}
47
48/* scrolling (content) region style */
49
50div#main {
51 margin: 0;
52 padding: 1em;
53 width: 100%;
54 clear: both;
55}
56
57/* sections */
58
59div#header {
60 font-size: 70%;
61 color: #666666;
62 margin-bottom: 0.5em;
63}
64
65div.section {
66 margin-bottom: 1em;
67}
68
69div.sectionTitle {
70 display: inline;
71 font-size: 120%;
72 font-weight: bold;
73 color: #003399;
74}
75
76div.sectionContent {
77 margin-top: 0.2em;
78}
79
80span.subsectionTitle {
81 font-weight: bold;
82}
83
84div#footer {
85 margin-top: 1em;
86 border-top: thin solid #003399;
87 padding-top: 0.5em;
88}
89
90/* authored content (block) */
91
92p {
93 margin-top: 0;
94 margin-bottom: 1em;
95}
96
97dl {
98 margin-top: 0;
99 margin-bottom: 1em;
100}
101
102div.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
114pre {
115 margin: 0;
116 padding: 0;
117}
118
119table.authoredTable {
120 table-layout: fixed;
121 width: 100%;
122 margin-bottom: 1em;
123}
124
125table.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
136table.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
145div.alert {
146 border: 1px solid #C8CDDE;
147 background: #F7F7FF;
148}
149
150div.media {
151 text-align: center;
152 margin-bottom: 1em;
153}
154
155
156/* authored content (inline) */
157
158span.keyword {
159 font-weight: bold;
160}
161
162span.code {
163 font-family: "Andale Mono", "Courier New", Courier, monospace;
164}
165
166/* auto-generated controls */
167
168div.langTabs {
169 width: 100%;
170}
171
172div.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
185div.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
198table.members {
199 table-layout: fixed;
200 width: 100%;
201}
202
203table.members th.iconColumn {
204 width: 60px;
205}
206
207table.members th.nameColumn {
208 width: 33%;
209}
210
211table.members th.descriptionColumn {
212 width: 66%;
213}
214
215table.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
226table.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
236table.exceptions {
237 table-layout: fixed;
238 width: 100%;
239}
240
241
242table.exceptions th.exceptionNameColumn {
243 width: 33%;
244}
245
246table.exceptions th.exceptionConditionColumn {
247 width: 66%;
248}
249
250table.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
261table.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
270table.permissions {
271 table-layout: fixed;
272 width: 100%;
273}
274
275
276table.permissions th.permissionNameColumn {
277 width: 33%;
278}
279
280table.permissions th.permissionConditionColumn {
281 width: 66%;
282}
283
284table.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
295table.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
304span.obsolete {
305 color: red;
306}
307
308span.cs {
309 display: inline;
310}
311
312span.vb {
313 display: none;
314}
315
316span.cpp {
317 display: none;
318}
319/* syntax styling */
320
321div.code span.identifier {
322 font-size: 120%;
323 font-weight: bold;
324}
325
326div.code span.keyword {
327 color: green;
328}
329
330div.code span.parameter {
331 font-style: italic;
332 color: purple;
333}
334
335div.code span.literal {
336 color: purple;
337}
338
339div.code span.comment {
340 color: red;
341}
342
343span.foreignPhrase {
344 font-style: italic;
345}
346
347span.placeholder {
348 font-style: italic;
349}
350
351a {
352 color: blue;
353 font-weight: bold;
354 text-decoration: none;
355}
356
357MSHelp\:link {
358 color: blue;
359 font-weight: bold;
360 hoverColor: #3366ff;
361}
362
363span.nolink {
364 font-weight: bold;
365}
366
367table.filter {
368 table-layout: fixed;
369}
370
371tr.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
382tr.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
392td.line {
393 background: #EFEFF7;
394}
diff --git a/src/tools/Dtf/Documents/Reference/Compression.htm b/src/tools/Dtf/Documents/Reference/Compression.htm
new file mode 100644
index 00000000..7782bea1
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/Compression.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Reference/Compression1.png b/src/tools/Dtf/Documents/Reference/Compression1.png
new file mode 100644
index 00000000..5b2e177f
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/Compression1.png
Binary files differ
diff --git a/src/tools/Dtf/Documents/Reference/Compression2.png b/src/tools/Dtf/Documents/Reference/Compression2.png
new file mode 100644
index 00000000..394a5f18
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/Compression2.png
Binary files differ
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller.htm b/src/tools/Dtf/Documents/Reference/WindowsInstaller.htm
new file mode 100644
index 00000000..28990ce4
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller.htm
@@ -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>
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller1.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller1.png
new file mode 100644
index 00000000..cc769cc7
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller1.png
Binary files differ
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png
new file mode 100644
index 00000000..0c11e501
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller2.png
Binary files differ
diff --git a/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png b/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png
new file mode 100644
index 00000000..68acd7d8
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/WindowsInstaller3.png
Binary files differ
diff --git a/src/tools/Dtf/Documents/Reference/dtfref.shfbproj b/src/tools/Dtf/Documents/Reference/dtfref.shfbproj
new file mode 100644
index 00000000..e45d2a07
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/dtfref.shfbproj
@@ -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>
diff --git a/src/tools/Dtf/Documents/Reference/helplink.js b/src/tools/Dtf/Documents/Reference/helplink.js
new file mode 100644
index 00000000..a4989824
--- /dev/null
+++ b/src/tools/Dtf/Documents/Reference/helplink.js
@@ -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
3FixHelpLinks();
4
5function 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
150function 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.
167function 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}
diff --git a/src/tools/Dtf/Inventory/Columns.resx b/src/tools/Dtf/Inventory/Columns.resx
new file mode 100644
index 00000000..cfeb11e3
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Columns.resx
@@ -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
diff --git a/src/tools/Dtf/Inventory/Features.cs b/src/tools/Dtf/Inventory/Features.cs
new file mode 100644
index 00000000..9d2747ba
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Features.cs
@@ -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
3using System;
4using System.IO;
5using System.Data;
6using System.Collections;
7using System.Collections.Generic;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/IInventoryDataProvider.cs b/src/tools/Dtf/Inventory/IInventoryDataProvider.cs
new file mode 100644
index 00000000..affcb358
--- /dev/null
+++ b/src/tools/Dtf/Inventory/IInventoryDataProvider.cs
@@ -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
3using System;
4using System.Data;
5
6namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/Inventory.cs b/src/tools/Dtf/Inventory/Inventory.cs
new file mode 100644
index 00000000..07735086
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.cs
@@ -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
3using System;
4using System.IO;
5using System.Drawing;
6using System.Collections;
7using System.ComponentModel;
8using System.Diagnostics.CodeAnalysis;
9using System.Windows.Forms;
10using System.Globalization;
11using System.Reflection;
12using System.Resources;
13using System.Threading;
14using System.Security.Permissions;
15using 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
25namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/Inventory.csproj b/src/tools/Dtf/Inventory/Inventory.csproj
new file mode 100644
index 00000000..57bae907
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.csproj
@@ -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.Tools.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>
diff --git a/src/tools/Dtf/Inventory/Inventory.ico b/src/tools/Dtf/Inventory/Inventory.ico
new file mode 100644
index 00000000..d5757f7a
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.ico
Binary files differ
diff --git a/src/tools/Dtf/Inventory/Inventory.resx b/src/tools/Dtf/Inventory/Inventory.resx
new file mode 100644
index 00000000..9aeb4d2c
--- /dev/null
+++ b/src/tools/Dtf/Inventory/Inventory.resx
@@ -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
diff --git a/src/tools/Dtf/Inventory/components.cs b/src/tools/Dtf/Inventory/components.cs
new file mode 100644
index 00000000..b516af46
--- /dev/null
+++ b/src/tools/Dtf/Inventory/components.cs
@@ -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
3using System;
4using System.IO;
5using System.Data;
6using System.Text;
7using System.Collections;
8using System.Collections.Generic;
9using System.Globalization;
10using System.Windows.Forms;
11using Microsoft.Win32;
12using WixToolset.Dtf.WindowsInstaller;
13using View = WixToolset.Dtf.WindowsInstaller.View;
14
15namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/msiutils.cs b/src/tools/Dtf/Inventory/msiutils.cs
new file mode 100644
index 00000000..189d28a9
--- /dev/null
+++ b/src/tools/Dtf/Inventory/msiutils.cs
@@ -0,0 +1,46 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Collections;
5using WixToolset.Dtf.WindowsInstaller;
6
7
8namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/patches.cs b/src/tools/Dtf/Inventory/patches.cs
new file mode 100644
index 00000000..ca96a97d
--- /dev/null
+++ b/src/tools/Dtf/Inventory/patches.cs
@@ -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
3using System;
4using System.IO;
5using System.Security;
6using System.Data;
7using System.Collections;
8using System.Globalization;
9using System.Windows.Forms;
10using WixToolset.Dtf.WindowsInstaller;
11
12namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/products.cs b/src/tools/Dtf/Inventory/products.cs
new file mode 100644
index 00000000..635e5439
--- /dev/null
+++ b/src/tools/Dtf/Inventory/products.cs
@@ -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
3using System;
4using System.Data;
5using System.Globalization;
6using System.Collections;
7using System.Windows.Forms;
8using WixToolset.Dtf.WindowsInstaller;
9
10namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/Inventory/xp.manifest b/src/tools/Dtf/Inventory/xp.manifest
new file mode 100644
index 00000000..34d61fea
--- /dev/null
+++ b/src/tools/Dtf/Inventory/xp.manifest
@@ -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>
diff --git a/src/tools/Dtf/WiFile/WiFile.cs b/src/tools/Dtf/WiFile/WiFile.cs
new file mode 100644
index 00000000..1e5c80df
--- /dev/null
+++ b/src/tools/Dtf/WiFile/WiFile.cs
@@ -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
3using System;
4using System.IO;
5using System.Reflection;
6using System.Collections;
7using System.Collections.Generic;
8using System.Diagnostics.CodeAnalysis;
9using System.Globalization;
10using System.Text.RegularExpressions;
11using WixToolset.Dtf.WindowsInstaller;
12using 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>
20public 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}
diff --git a/src/tools/Dtf/WiFile/WiFile.csproj b/src/tools/Dtf/WiFile/WiFile.csproj
new file mode 100644
index 00000000..c8d39235
--- /dev/null
+++ b/src/tools/Dtf/WiFile/WiFile.csproj
@@ -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.Tools.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>
diff --git a/src/tools/Dtf/XPack/AssemblyInfo.cs b/src/tools/Dtf/XPack/AssemblyInfo.cs
new file mode 100644
index 00000000..6dfb9437
--- /dev/null
+++ b/src/tools/Dtf/XPack/AssemblyInfo.cs
@@ -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
3using System.Reflection;
4
5[assembly: AssemblyDescription("Simple command-line CAB/ZIP packing and unpacking tool.")]
diff --git a/src/tools/Dtf/XPack/XPack.cs b/src/tools/Dtf/XPack/XPack.cs
new file mode 100644
index 00000000..cc52bb7d
--- /dev/null
+++ b/src/tools/Dtf/XPack/XPack.cs
@@ -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
3namespace WixToolset.Dtf.Tools.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}
diff --git a/src/tools/Dtf/XPack/XPack.csproj b/src/tools/Dtf/XPack/XPack.csproj
new file mode 100644
index 00000000..3e76de2e
--- /dev/null
+++ b/src/tools/Dtf/XPack/XPack.csproj
@@ -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.Tools.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>
diff --git a/src/tools/ThmViewerPackage/Package.wxs b/src/tools/ThmViewerPackage/Package.wxs
new file mode 100644
index 00000000..dae6a9d0
--- /dev/null
+++ b/src/tools/ThmViewerPackage/Package.wxs
@@ -0,0 +1,30 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Package Name="WiX Toolset Theme Viewer" Manufacturer="WiX Toolset" Language="1033" Version="!(bind.fileVersion.ThmViewerFile)" UpgradeCode="59c4b122-5167-445b-8fc4-09dcd4eced89">
4 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
5 <MediaTemplate EmbedCab="yes" />
6
7 <Feature Id="Main">
8 <ComponentGroupRef Id="Components" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <ComponentGroup Id="Components" Directory="INSTALLFOLDER" Subdirectory="bin">
14 <Component>
15 <File Id="ThmViewerFile" Source="thmviewer.exe" />
16 <Shortcut Name="!(bind.property.ProductName)" Directory="ShortcutFolder" Advertise="yes" />
17 </Component>
18 </ComponentGroup>
19 </Fragment>
20
21 <Fragment>
22 <StandardDirectory Id="ProgramFilesFolder">
23 <Directory Id="INSTALLFOLDER" Name="WiX Toolset v4.0" />
24 </StandardDirectory>
25 <StandardDirectory Id="ProgramMenuFolder">
26 <Directory Id="ShortcutFolder" Name="WiX Toolset" />
27 </StandardDirectory>
28 </Fragment>
29
30</Wix>
diff --git a/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj b/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj
new file mode 100644
index 00000000..9b2a2b02
--- /dev/null
+++ b/src/tools/ThmViewerPackage/ThmViewerPackage.wixproj
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <OutputPath>$(PackageOutputPath)</OutputPath>
5 </PropertyGroup>
6
7 <ItemGroup>
8 <ProjectReference Include="..\thmviewer\thmviewer.vcxproj" />
9 </ItemGroup>
10</Project>
diff --git a/src/tools/WixToolset.Templates/WixToolset.Templates.csproj b/src/tools/WixToolset.Templates/WixToolset.Templates.csproj
new file mode 100644
index 00000000..00b6c6d3
--- /dev/null
+++ b/src/tools/WixToolset.Templates/WixToolset.Templates.csproj
@@ -0,0 +1,20 @@
1<Project Sdk="Microsoft.NET.Sdk">
2 <PropertyGroup>
3 <PackageType>Template</PackageType>
4 <Title>WixToolset Templates</Title>
5 <Description>Project and item template for the WiX Toolset.</Description>
6
7 <TargetFramework>netstandard2.0</TargetFramework>
8 <IncludeBuildOutput>false</IncludeBuildOutput>
9 <NoWarn>$(NoWarn);NU5128</NoWarn>
10
11 <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
12 <IncludeContentInPack>true</IncludeContentInPack>
13 <ContentTargetFolders>content</ContentTargetFolders>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Compile Remove="**" />
18 <Content Include="templates\**" />
19 </ItemGroup>
20</Project>
diff --git a/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json b/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json
new file mode 100644
index 00000000..2bd67b11
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/FragmentItem/.template.config/template.json
@@ -0,0 +1,14 @@
1{
2 "$schema": "http://json.schemastore.org/template",
3 "identity": "WixToolset.Templates.FragmentItem",
4 "name": "WiX Toolset Fragment",
5 "shortName": "fragment",
6 "author": "WiX Toolset Team",
7 "classifications": ["WixToolset", "Code"],
8 "tags": {
9 "language": "WiX",
10 "type": "item"
11 },
12 "sourceName": "$safeprojectname$",
13 "defaultName": "Fragment"
14}
diff --git a/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs b/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs
new file mode 100644
index 00000000..12e1bfc3
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/FragmentItem/Fragment.wxs
@@ -0,0 +1,4 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 </Fragment>
4</Wix>
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj
new file mode 100644
index 00000000..c3536151
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/LibraryProject/$safeprojectname$.wixproj
@@ -0,0 +1,5 @@
1<Project Sdk="WixToolset.Sdk/4.0.0">
2 <PropertyGroup>
3 <OutputType>Library</OutputType>
4 </PropertyGroup>
5</Project>
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json
new file mode 100644
index 00000000..ddfa82e9
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/LibraryProject/.template.config/template.json
@@ -0,0 +1,14 @@
1{
2 "$schema": "http://json.schemastore.org/template",
3 "identity": "WixToolset.Templates.LibraryProject",
4 "name": "WiX Toolset Library",
5 "shortName": "wixlib",
6 "author": "WiX Toolset Team",
7 "classifications": ["WixToolset", "Library"],
8 "tags": {
9 "language": "WiX",
10 "type": "project"
11 },
12 "sourceName": "$safeprojectname$",
13 "defaultName": "Library"
14}
diff --git a/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs b/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs
new file mode 100644
index 00000000..12e1bfc3
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/LibraryProject/Fragment.wxs
@@ -0,0 +1,4 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 </Fragment>
4</Wix>
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj
new file mode 100644
index 00000000..cc813aab
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/ModuleProject/$safeprojectname$.wixproj
@@ -0,0 +1,5 @@
1<Project Sdk="WixToolset.Sdk/4.0.0">
2 <PropertyGroup>
3 <OutputType>Module</OutputType>
4 </PropertyGroup>
5</Project>
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json
new file mode 100644
index 00000000..2be72f83
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/ModuleProject/.template.config/template.json
@@ -0,0 +1,26 @@
1{
2 "$schema": "http://json.schemastore.org/template",
3 "identity": "WixToolset.Templates.ModuleProject",
4 "name": "WiX Toolset Module",
5 "shortName": "msm",
6 "author": "WiX Toolset Team",
7 "classifications": ["WixToolset", "Module"],
8 "tags": {
9 "language": "WiX",
10 "type": "project"
11 },
12 "symbols": {
13 "company": {
14 "type": "parameter",
15 "defaultValue": "Your Company Name",
16 "replaces": "$company$"
17 },
18 "version": {
19 "type": "parameter",
20 "defaultValue": "0.0.1",
21 "replaces": "$version$"
22 }
23 },
24 "sourceName": "$safeprojectname$",
25 "defaultName": "Module"
26}
diff --git a/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs b/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs
new file mode 100644
index 00000000..592c844d
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/ModuleProject/Module.wxs
@@ -0,0 +1,8 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Module Id="$safeprojectname$" Guid="PUT-GUID-HERE" Manufacturer="$company$" Language="0" Version="$version$">
3 <Component Guid='PUT-GUID-HERE' Directory='TARGETDIR'>
4 <!-- TODO: Install something more useful than this source code file itself -->
5 <File Source="Module.wxs" />
6 </Component>
7 </Module>
8</Wix>
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj b/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj
new file mode 100644
index 00000000..17640703
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/PackageProject/$safeprojectname$.wixproj
@@ -0,0 +1,2 @@
1<Project Sdk="WixToolset.Sdk/4.0.0">
2</Project>
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json b/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json
new file mode 100644
index 00000000..d12f3109
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/PackageProject/.template.config/template.json
@@ -0,0 +1,26 @@
1{
2 "$schema": "http://json.schemastore.org/template",
3 "identity": "WixToolset.Templates.PackageProject",
4 "name": "WiX Toolset Package",
5 "shortName": "msi",
6 "author": "WiX Toolset Team",
7 "classifications": ["WixToolset", "Package"],
8 "tags": {
9 "language": "WiX",
10 "type": "project"
11 },
12 "symbols": {
13 "company": {
14 "type": "parameter",
15 "defaultValue": "Your Company Name",
16 "replaces": "$company$"
17 },
18 "version": {
19 "type": "parameter",
20 "defaultValue": "0.0.1",
21 "replaces": "$version$"
22 }
23 },
24 "sourceName": "$safeprojectname$",
25 "defaultName": "Package"
26}
diff --git a/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs b/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs
new file mode 100644
index 00000000..ac69e549
--- /dev/null
+++ b/src/tools/WixToolset.Templates/templates/PackageProject/Package.wxs
@@ -0,0 +1,24 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="$safeprojectname$" Manufacturer="$company$" Version="$version$" UpgradeCode="PUT-GUID-HERE">
3 <MajorUpgrade DowngradeError="A newer version of [ProductName] is already installed" />
4
5 <Feature Id="Main">
6 <ComponentGroupRef Id="Components" />
7 </Feature>
8 </Package>
9
10 <Fragment>
11 <ComponentGroup Id="Components" Directory="INSTALLFOLDER">
12 <Component>
13 <!-- TODO: Install something more useful than this source code file itself -->
14 <File Source="Package.wxs" />
15 </Component>
16 </ComponentGroup>
17 </Fragment>
18
19 <Fragment>
20 <StandardDirectory Id="ProgramFiles6432Folder">
21 <Directory Id="INSTALLFOLDER" Name="!(Property.Manufacturer) !(Property.ProductName)" />
22 </StandardDirectory>
23 </Fragment>
24</Wix>
diff --git a/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs b/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs
new file mode 100644
index 00000000..964b976e
--- /dev/null
+++ b/src/tools/burn/ManagedBundleRunner/BundleErrorEventArgs.cs
@@ -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
3namespace WixToolset.Tools
4{
5 using System;
6
7 /// <summary>
8 /// Arguments provided when bundle encounters an error.
9 /// </summary>
10 [Serializable]
11 public class BundleErrorEventArgs : EventArgs
12 {
13 /// <summary>
14 /// Gets the error code.
15 /// </summary>
16 public int Code { get; set; }
17
18 /// <summary>
19 /// Gets the error message.
20 /// </summary>
21 public string Message { get; set; }
22
23 /// <summary>
24 /// Gets the recommended display flags for an error dialog.
25 /// </summary>
26 public int UIHint { get; set; }
27
28 /// <summary>
29 /// Gets or sets the <see cref="Result"/> of the operation. This is passed back to the bundle.
30 /// </summary>
31 public BundleResult Result { get; set; }
32 }
33}
diff --git a/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs b/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs
new file mode 100644
index 00000000..2add7cca
--- /dev/null
+++ b/src/tools/burn/ManagedBundleRunner/BundleProgressEventArgs.cs
@@ -0,0 +1,23 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 using System;
6
7 /// <summary>
8 /// Arguments provided when bundle progress is updated.
9 /// </summary>
10 [Serializable]
11 public class BundleProgressEventArgs : EventArgs
12 {
13 /// <summary>
14 /// Gets the percentage from 0 to 100 completed for a bundle.
15 /// </summary>
16 public int Progress { get; set; }
17
18 /// <summary>
19 /// Gets or sets the <see cref="Result"/> of the operation. This is passed back to the bundle.
20 /// </summary>
21 public BundleResult Result { get; set; }
22 }
23}
diff --git a/src/tools/burn/ManagedBundleRunner/BundleResult.cs b/src/tools/burn/ManagedBundleRunner/BundleResult.cs
new file mode 100644
index 00000000..5df0d699
--- /dev/null
+++ b/src/tools/burn/ManagedBundleRunner/BundleResult.cs
@@ -0,0 +1,24 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 /// <summary>
6 /// Result codes.
7 /// </summary>
8 public enum BundleResult
9 {
10 Error = -1,
11 None,
12 Ok,
13 Cancel,
14 Abort,
15 Retry,
16 Ignore,
17 Yes,
18 No,
19 Close,
20 Help,
21 TryAgain,
22 Continue,
23 }
24}
diff --git a/src/tools/burn/ManagedBundleRunner/BundleRunner.cs b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs
new file mode 100644
index 00000000..98d92c66
--- /dev/null
+++ b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs
@@ -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
3namespace WixToolset.Tools
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO.Pipes;
8 using System.Text;
9 using System.Threading;
10
11 /// <summary>
12 /// Runs a bundle with provided command-line.
13 /// </summary>
14 public class BundleRunner
15 {
16 /// <summary>
17 /// Creates a runner for the provided bundle.
18 /// </summary>
19 /// <param name="bundle">Path to the bundle to run.</param>
20 public BundleRunner(string bundle)
21 {
22 this.Path = bundle;
23 }
24
25 /// <summary>
26 /// Fired when the bundle encounters an error.
27 /// </summary>
28 public event EventHandler<BundleErrorEventArgs> Error;
29
30 /// <summary>
31 /// Fired when the bundle progress is udpated.
32 /// </summary>
33 public event EventHandler<BundleProgressEventArgs> Progress;
34
35 /// <summary>
36 /// Gets the path to the bundle to run.
37 /// </summary>
38 public string Path { get; private set; }
39
40 /// <summary>
41 /// Runs the bundle with the provided command-line.
42 /// </summary>
43 /// <param name="commandLine">Optional command-line to pass to the bundle.</param>
44 /// <returns>Exit code from the bundle.</returns>
45 public int Run(string commandLine = null)
46 {
47 WaitHandle[] waits = new WaitHandle[] { new ManualResetEvent(false), new ManualResetEvent(false) };
48 int returnCode = 0;
49 int pid = Process.GetCurrentProcess().Id;
50 string pipeName = String.Concat("bpe_", pid);
51 string pipeSecret = Guid.NewGuid().ToString("N");
52
53 using (NamedPipeServerStream pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1))
54 {
55 using (Process bundleProcess = new Process())
56 {
57 bundleProcess.StartInfo.FileName = this.Path;
58 bundleProcess.StartInfo.Arguments = String.Format("{0} -burn.embedded {1} {2} {3}", commandLine ?? String.Empty, pipeName, pipeSecret, pid);
59 bundleProcess.StartInfo.UseShellExecute = false;
60 bundleProcess.StartInfo.CreateNoWindow = true;
61 bundleProcess.Start();
62
63 Connect(pipe, pipeSecret, pid, bundleProcess.Id);
64
65 PumpMessages(pipe);
66
67 bundleProcess.WaitForExit();
68 returnCode = bundleProcess.ExitCode;
69 }
70 }
71
72 return returnCode;
73 }
74
75 /// <summary>
76 /// Called when bundle encounters an error.
77 /// </summary>
78 /// <param name="e">Additional arguments for this event.</param>
79 protected virtual void OnError(BundleErrorEventArgs e)
80 {
81 EventHandler<BundleErrorEventArgs> handler = this.Error;
82 if (handler != null)
83 {
84 handler(this, e);
85 }
86 }
87
88 /// <summary>
89 /// Called when bundle progress is updated.
90 /// </summary>
91 /// <param name="e">Additional arguments for this event.</param>
92 protected virtual void OnProgress(BundleProgressEventArgs e)
93 {
94 EventHandler<BundleProgressEventArgs> handler = this.Progress;
95 if (handler != null)
96 {
97 handler(this, e);
98 }
99 }
100
101 private void Connect(NamedPipeServerStream pipe, string pipeSecret, int pid, int childPid)
102 {
103 pipe.WaitForConnection();
104
105 WriteSecretToPipe(pipe, pipeSecret);
106
107 WriteNumberToPipe(pipe, (uint)pid);
108
109 uint ack = ReadNumberFromPipe(pipe);
110 // This is not true when bundle is run under a debugger
111 //if (ack != childPid)
112 //{
113 // throw new ApplicationException("Incorrect child process.");
114 //}
115 }
116
117 private void PumpMessages(NamedPipeServerStream pipe)
118 {
119 uint messageId;
120 while (TryReadNumberFromPipe(pipe, out messageId))
121 {
122 uint messageSize = ReadNumberFromPipe(pipe);
123
124 BundleResult result = BundleResult.None;
125 switch (messageId)
126 {
127 case 1: //error
128 result = ProcessErrorMessage(pipe);
129 break;
130
131 case 2: // progress
132 result = ProcessProgressMessage(pipe);
133 break;
134
135 default: // unknown message, do nothing.
136 break;
137 }
138
139 CompleteMessage(pipe, result);
140 }
141 }
142
143 private BundleResult ProcessErrorMessage(NamedPipeServerStream pipe)
144 {
145 BundleErrorEventArgs e = new BundleErrorEventArgs();
146 e.Code = (int)ReadNumberFromPipe(pipe);
147 e.Message = ReadStringFromPipe(pipe);
148 e.UIHint = (int)ReadNumberFromPipe(pipe);
149
150 this.OnError(e);
151
152 return e.Result;
153 }
154
155 private BundleResult ProcessProgressMessage(NamedPipeServerStream pipe)
156 {
157 ReadNumberFromPipe(pipe); // eat the first progress number because it is always zero.
158
159 BundleProgressEventArgs e = new BundleProgressEventArgs();
160 e.Progress = (int)ReadNumberFromPipe(pipe);
161
162 this.OnProgress(e);
163
164 return e.Result;
165 }
166
167 private void CompleteMessage(NamedPipeServerStream pipe, BundleResult result)
168 {
169 uint complete = 0xF0000002;
170 WriteNumberToPipe(pipe, complete);
171 WriteNumberToPipe(pipe, 4); // size of message data
172 WriteNumberToPipe(pipe, (uint)result);
173 }
174
175 private uint ReadNumberFromPipe(NamedPipeServerStream pipe)
176 {
177 byte[] buffer = new byte[4];
178 pipe.Read(buffer, 0, buffer.Length);
179 return BitConverter.ToUInt32(buffer, 0);
180 }
181
182 private string ReadStringFromPipe(NamedPipeServerStream pipe)
183 {
184 uint length = ReadNumberFromPipe(pipe);
185
186 byte[] buffer = new byte[length * 2];
187 pipe.Read(buffer, 0, buffer.Length);
188
189 return Encoding.Unicode.GetString(buffer);
190 }
191
192 private bool TryReadNumberFromPipe(NamedPipeServerStream pipe, out uint value)
193 {
194 value = ReadNumberFromPipe(pipe); // reading will not block and return zero if pipe is not connected.
195 return pipe.IsConnected;
196 }
197
198 private void WriteNumberToPipe(NamedPipeServerStream pipe, uint value)
199 {
200 byte[] buffer = BitConverter.GetBytes(value);
201 pipe.Write(buffer, 0, buffer.Length);
202 }
203
204 private void WriteSecretToPipe(NamedPipeServerStream pipe, string secret)
205 {
206 byte[] buffer = Encoding.Unicode.GetBytes(secret);
207
208 WriteNumberToPipe(pipe, (uint)buffer.Length);
209 pipe.Write(buffer, 0, buffer.Length);
210 }
211 }
212}
diff --git a/src/tools/burn/runbundle/AssemblyInfo.cs b/src/tools/burn/runbundle/AssemblyInfo.cs
new file mode 100644
index 00000000..3a66d5e3
--- /dev/null
+++ b/src/tools/burn/runbundle/AssemblyInfo.cs
@@ -0,0 +1,12 @@
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
3using System;
4using System.Reflection;
5using System.Runtime.CompilerServices;
6using System.Runtime.InteropServices;
7
8[assembly: AssemblyTitle("Executable to demonstrate Bundle Runner Sample")]
9[assembly: AssemblyDescription("")]
10[assembly: AssemblyCulture("")]
11[assembly: CLSCompliant(true)]
12[assembly: ComVisible(false)]
diff --git a/src/tools/burn/runbundle/Program.cs b/src/tools/burn/runbundle/Program.cs
new file mode 100644
index 00000000..fd36c2ca
--- /dev/null
+++ b/src/tools/burn/runbundle/Program.cs
@@ -0,0 +1,47 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 using System;
6 using System.Linq;
7 using WixToolset.Tools;
8
9 /// <summary>
10 /// Example executable that installs then immediately uninstalls a bundle showing progress.
11 /// </summary>
12 class Program
13 {
14 static int Main(string[] args)
15 {
16 if (args.Length == 0)
17 {
18 Console.WriteLine("Must provide the path to the bundle to install then uninstall.");
19 return -1;
20 }
21
22 BundleRunner runner = new BundleRunner(args[0]);
23 runner.Error += Program.OnError;
24 runner.Progress += Program.OnProgress;
25
26 Console.WriteLine("Installing: {0}", runner.Path);
27 int exitCode = runner.Run(String.Join(" ", args.Skip(1).ToArray()));
28 if (0 == exitCode)
29 {
30 Console.WriteLine("\r\nUninstalling: {0}", runner.Path);
31 exitCode = runner.Run("-uninstall");
32 }
33
34 return exitCode;
35 }
36
37 static void OnError(object sender, BundleErrorEventArgs e)
38 {
39 Console.WriteLine("error: {0}, uiHint: {1}, message: {2}", e.Code, e.UIHint, e.Message);
40 }
41
42 static void OnProgress(object sender, BundleProgressEventArgs e)
43 {
44 Console.WriteLine("progresss: {0}%", e.Progress);
45 }
46 }
47}
diff --git a/src/tools/thmviewer/Resources/LoremIpsum.rtf b/src/tools/thmviewer/Resources/LoremIpsum.rtf
new file mode 100644
index 00000000..1ab0e65b
--- /dev/null
+++ b/src/tools/thmviewer/Resources/LoremIpsum.rtf
Binary files differ
diff --git a/src/tools/thmviewer/Resources/thm.xml b/src/tools/thmviewer/Resources/thm.xml
new file mode 100644
index 00000000..6394f0f1
--- /dev/null
+++ b/src/tools/thmviewer/Resources/thm.xml
@@ -0,0 +1,11 @@
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<Theme>
6 <Font Id="0" Height="-48" Weight="700" Foreground="$windowtext">Consolas</Font>
7 <Font Id="1" Height="-12" Weight="700" Foreground="$windowtext" Background="$window">Consolas</Font>
8 <Window Width="220" Height="200" FontId="0" Caption="Theme Viewer">
9 <TreeView Name="Tree" X="0" Y="0" Width="0" Height="0" FontId="1" Visible="yes" FullRowSelect="yes" HasButtons="yes" AlwaysShowSelect="yes" HasLines="yes" LinesAtRoot="yes"/>
10 </Window>
11</Theme>
diff --git a/src/tools/thmviewer/display.cpp b/src/tools/thmviewer/display.cpp
new file mode 100644
index 00000000..444c6cfb
--- /dev/null
+++ b/src/tools/thmviewer/display.cpp
@@ -0,0 +1,339 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static const LPCWSTR THMVWR_WINDOW_CLASS_DISPLAY = L"ThmViewerDisplay";
6
7struct DISPLAY_THREAD_CONTEXT
8{
9 HWND hWnd;
10 HINSTANCE hInstance;
11
12 HANDLE hInit;
13};
14
15static DWORD WINAPI DisplayThreadProc(
16 __in LPVOID pvContext
17 );
18static LRESULT CALLBACK DisplayWndProc(
19 __in HWND hWnd,
20 __in UINT uMsg,
21 __in WPARAM wParam,
22 __in LPARAM lParam
23 );
24static BOOL DisplayOnThmLoadedControl(
25 __in THEME* pTheme,
26 __in const THEME_LOADEDCONTROL_ARGS* args,
27 __in THEME_LOADEDCONTROL_RESULTS* results
28 );
29
30
31extern "C" HRESULT DisplayStart(
32 __in HINSTANCE hInstance,
33 __in HWND hWnd,
34 __out HANDLE *phThread,
35 __out DWORD* pdwThreadId
36 )
37{
38 HRESULT hr = S_OK;
39 HANDLE rgHandles[2] = { };
40 DISPLAY_THREAD_CONTEXT context = { };
41
42 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
43 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
44
45 context.hWnd = hWnd;
46 context.hInstance = hInstance;
47 context.hInit = rgHandles[0];
48
49 rgHandles[1] = ::CreateThread(NULL, 0, DisplayThreadProc, reinterpret_cast<LPVOID>(&context), 0, pdwThreadId);
50 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create display thread.");
51
52 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
53
54 *phThread = rgHandles[1];
55 rgHandles[1] = NULL;
56
57LExit:
58 ReleaseHandle(rgHandles[1]);
59 ReleaseHandle(rgHandles[0]);
60 return hr;
61}
62
63static DWORD WINAPI DisplayThreadProc(
64 __in LPVOID pvContext
65 )
66{
67 HRESULT hr = S_OK;
68
69 DISPLAY_THREAD_CONTEXT* pContext = static_cast<DISPLAY_THREAD_CONTEXT*>(pvContext);
70 HINSTANCE hInstance = pContext->hInstance;
71 HWND hwndParent = pContext->hWnd;
72
73 // We can signal the initialization event as soon as we have copied the context
74 // values into local variables.
75 ::SetEvent(pContext->hInit);
76
77 BOOL fComInitialized = FALSE;
78
79 HANDLE_THEME* pCurrentHandle = NULL;
80 ATOM atomWc = 0;
81 WNDCLASSW wc = { };
82 HWND hWnd = NULL;
83 RECT rc = { };
84 int x = CW_USEDEFAULT;
85 int y = CW_USEDEFAULT;
86
87 BOOL fRedoMsg = FALSE;
88 BOOL fRet = FALSE;
89 MSG msg = { };
90
91 BOOL fCreateIfNecessary = FALSE;
92
93 hr = ::CoInitialize(NULL);
94 ExitOnFailure(hr, "Failed to initialize COM on display thread.");
95 fComInitialized = TRUE;
96
97 // As long as the parent window is alive and kicking, keep this thread going (with or without a theme to display ).
98 while (::IsWindow(hwndParent))
99 {
100 if (pCurrentHandle && fCreateIfNecessary)
101 {
102 THEME* pTheme = pCurrentHandle->pTheme;
103
104 if (CW_USEDEFAULT == x && CW_USEDEFAULT == y && ::GetWindowRect(hwndParent, &rc))
105 {
106 x = rc.left;
107 y = rc.bottom + 20;
108 }
109
110 hr = ThemeCreateParentWindow(pTheme, 0, wc.lpszClassName, pTheme->sczCaption, pTheme->dwStyle, x, y, hwndParent, hInstance, pCurrentHandle, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
111 ExitOnFailure(hr, "Failed to create display window.");
112
113 fCreateIfNecessary = FALSE;
114 }
115
116 // message pump
117 while (fRedoMsg || 0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
118 {
119 if (fRedoMsg)
120 {
121 fRedoMsg = FALSE;
122 }
123
124 if (-1 == fRet)
125 {
126 hr = E_UNEXPECTED;
127 ExitOnFailure(hr, "Unexpected return value from display message pump.");
128 }
129 else if (NULL == msg.hwnd) // Thread message.
130 {
131 if (WM_THMVWR_NEW_THEME == msg.message)
132 {
133 // If there is already a handle, release it.
134 if (pCurrentHandle)
135 {
136 DecrementHandleTheme(pCurrentHandle);
137 pCurrentHandle = NULL;
138 }
139
140 // If the window was created, remember its window location before we destroy
141 // it so so we can open the new window in the same place.
142 if (::IsWindow(hWnd))
143 {
144 ::GetWindowRect(hWnd, &rc);
145 x = rc.left;
146 y = rc.top;
147
148 ::DestroyWindow(hWnd);
149 }
150
151 // If the display window class was registered, unregister it so we can
152 // reuse the same window class name for the new theme.
153 if (atomWc)
154 {
155 if (!::UnregisterClassW(reinterpret_cast<LPCWSTR>(atomWc), hInstance))
156 {
157 DWORD er = ::GetLastError();
158 er = er;
159 }
160
161 atomWc = 0;
162 }
163
164 // If we were provided a new theme handle, create a new window class to
165 // support it.
166 pCurrentHandle = reinterpret_cast<HANDLE_THEME*>(msg.lParam);
167 if (pCurrentHandle)
168 {
169 ThemeInitializeWindowClass(pCurrentHandle->pTheme, &wc, DisplayWndProc, hInstance, THMVWR_WINDOW_CLASS_DISPLAY);
170
171 atomWc = ::RegisterClassW(&wc);
172 if (!atomWc)
173 {
174 ExitWithLastError(hr, "Failed to register display window class.");
175 }
176 }
177 }
178 else if (WM_THMVWR_SHOWPAGE == msg.message)
179 {
180 if (pCurrentHandle && ::IsWindow(hWnd) && pCurrentHandle->pTheme->hwndParent == hWnd)
181 {
182 DWORD dwPageId = static_cast<DWORD>(msg.lParam);
183 int nCmdShow = static_cast<int>(msg.wParam);
184
185 // First show/hide the controls not associated with a page.
186 for (DWORD i = 0; i < pCurrentHandle->pTheme->cControls; ++i)
187 {
188 THEME_CONTROL* pControl = pCurrentHandle->pTheme->rgControls + i;
189 if (!pControl->wPageId)
190 {
191 ThemeShowControl(pControl, nCmdShow);
192 }
193 }
194
195 // If a page id was provided also, show/hide those controls
196 if (dwPageId)
197 {
198 // Ignore error since we aren't using variables and it can only fail when using variables.
199 ThemeShowPage(pCurrentHandle->pTheme, dwPageId, nCmdShow);
200 }
201 }
202 else // display window isn't visible or it doesn't match the current handle.
203 {
204 // Keep the current message around to try again after we break out of this loop
205 // and create the window.
206 fRedoMsg = TRUE;
207 fCreateIfNecessary = TRUE;
208 break;
209 }
210 }
211 }
212 else if (!ThemeHandleKeyboardMessage(pCurrentHandle->pTheme, hwndParent, &msg)) // Window message.
213 {
214 ::TranslateMessage(&msg);
215 ::DispatchMessageW(&msg);
216 }
217 }
218 }
219
220LExit:
221 if (::IsWindow(hWnd))
222 {
223 ::DestroyWindow(hWnd);
224 }
225
226 if (atomWc)
227 {
228 if (!::UnregisterClassW(THMVWR_WINDOW_CLASS_DISPLAY, hInstance))
229 {
230 DWORD er = ::GetLastError();
231 er = er;
232 }
233 }
234
235 DecrementHandleTheme(pCurrentHandle);
236
237 if (fComInitialized)
238 {
239 ::CoUninitialize();
240 }
241
242 return hr;
243}
244
245static LRESULT CALLBACK DisplayWndProc(
246 __in HWND hWnd,
247 __in UINT uMsg,
248 __in WPARAM wParam,
249 __in LPARAM lParam
250 )
251{
252 static DWORD dwProgress = 0;
253 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
254
255 switch (uMsg)
256 {
257 case WM_NCCREATE:
258 {
259 LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
260 pHandleTheme = reinterpret_cast<HANDLE_THEME*>(lpcs->lpCreateParams);
261 IncrementHandleTheme(pHandleTheme);
262 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandleTheme));
263 }
264 break;
265
266 case WM_TIMER:
267 if (!lParam && SUCCEEDED(ThemeSetProgressControl(reinterpret_cast<THEME_CONTROL*>(wParam), dwProgress)))
268 {
269 dwProgress += rand() % 10 + 1;
270 if (dwProgress > 100)
271 {
272 dwProgress = 0;
273 }
274
275 return 0;
276 }
277 break;
278
279 case WM_COMMAND:
280 {
281 WCHAR wzText[1024];
282 ::StringCchPrintfW(wzText, countof(wzText), L"Command %u\r\n", LOWORD(wParam));
283 OutputDebugStringW(wzText);
284 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
285 }
286 break;
287
288 case WM_SYSCOMMAND:
289 {
290 WCHAR wzText[1024];
291 ::StringCchPrintfW(wzText, countof(wzText), L"SysCommand %u\r\n", LOWORD(wParam));
292 OutputDebugStringW(wzText);
293 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
294 }
295 break;
296
297 case WM_NCDESTROY:
298 DecrementHandleTheme(pHandleTheme);
299 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
300 ::PostQuitMessage(0);
301 break;
302
303 case WM_THMUTIL_LOADED_CONTROL:
304 if (pHandleTheme)
305 {
306 return DisplayOnThmLoadedControl(pHandleTheme->pTheme, reinterpret_cast<THEME_LOADEDCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADEDCONTROL_RESULTS*>(lParam));
307 }
308 }
309
310 return ThemeDefWindowProc(pHandleTheme ? pHandleTheme->pTheme : NULL, hWnd, uMsg, wParam, lParam);
311}
312
313static BOOL DisplayOnThmLoadedControl(
314 __in THEME* pTheme,
315 __in const THEME_LOADEDCONTROL_ARGS* args,
316 __in THEME_LOADEDCONTROL_RESULTS* results
317 )
318{
319 HRESULT hr = S_OK;
320 const THEME_CONTROL* pControl = args->pThemeControl;
321
322 // Pre-populate some control types with data.
323 if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type)
324 {
325 hr = WnduLoadRichEditFromResource(pControl->hWnd, MAKEINTRESOURCEA(THMVWR_RES_RICHEDIT_FILE), ::GetModuleHandleW(NULL));
326 ExitOnFailure(hr, "Failed to load richedit text.");
327 }
328 else if (THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type)
329 {
330 UINT_PTR timerId = reinterpret_cast<UINT_PTR>(pControl);
331 UINT_PTR id = ::SetTimer(pTheme->hwndParent, timerId, 500, NULL);
332 id = id; // prevents warning in "ship" build.
333 Assert(id == timerId);
334 }
335
336LExit:
337 results->hr = hr;
338 return TRUE;
339}
diff --git a/src/tools/thmviewer/load.cpp b/src/tools/thmviewer/load.cpp
new file mode 100644
index 00000000..0267402a
--- /dev/null
+++ b/src/tools/thmviewer/load.cpp
@@ -0,0 +1,221 @@
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
5struct LOAD_THREAD_CONTEXT
6{
7 HWND hWnd;
8 LPCWSTR wzThemePath;
9 LPCWSTR wzWxlPath;
10
11 HANDLE hInit;
12};
13
14static DWORD WINAPI LoadThreadProc(
15 __in LPVOID pvContext
16 );
17
18
19extern "C" HRESULT LoadStart(
20 __in_z LPCWSTR wzThemePath,
21 __in_z_opt LPCWSTR wzWxlPath,
22 __in HWND hWnd,
23 __out HANDLE* phThread
24 )
25{
26 HRESULT hr = S_OK;
27 HANDLE rgHandles[2] = { };
28 LOAD_THREAD_CONTEXT context = { };
29
30 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
31 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
32
33 context.hWnd = hWnd;
34 context.wzThemePath = wzThemePath;
35 context.wzWxlPath = wzWxlPath;
36 context.hInit = rgHandles[0];
37
38 rgHandles[1] = ::CreateThread(NULL, 0, LoadThreadProc, &context, 0, NULL);
39 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create load thread.");
40
41 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
42
43 *phThread = rgHandles[1];
44 rgHandles[1] = NULL;
45
46LExit:
47 ReleaseHandle(rgHandles[1]);
48 ReleaseHandle(rgHandles[0]);
49 return hr;
50}
51
52
53static DWORD WINAPI LoadThreadProc(
54 __in LPVOID pvContext
55 )
56{
57 HRESULT hr = S_OK;
58 WIX_LOCALIZATION* pWixLoc = NULL;
59 LPWSTR sczThemePath = NULL;
60 LPWSTR sczWxlPath = NULL;
61 BOOL fComInitialized = FALSE;
62 HANDLE hDirectory = INVALID_HANDLE_VALUE;
63 LPWSTR sczDirectory = NULL;
64 LPWSTR wzFileName = NULL;
65
66 THEME* pTheme = NULL;
67 HANDLE_THEME* pHandle = NULL;
68
69 LOAD_THREAD_CONTEXT* pContext = static_cast<LOAD_THREAD_CONTEXT*>(pvContext);
70 HWND hWnd = pContext->hWnd;
71
72 hr = StrAllocString(&sczThemePath, pContext->wzThemePath, 0);
73 ExitOnFailure(hr, "Failed to copy path to initial theme file.");
74
75 if (pContext->wzWxlPath)
76 {
77 hr = StrAllocString(&sczWxlPath, pContext->wzWxlPath, 0);
78 ExitOnFailure(hr, "Failed to copy .wxl path to initial file.");
79 }
80
81 // We can signal the initialization event as soon as we have copied the context
82 // values into local variables.
83 ::SetEvent(pContext->hInit);
84
85 hr = ::CoInitialize(NULL);
86 ExitOnFailure(hr, "Failed to initialize COM on load thread.");
87 fComInitialized = TRUE;
88
89 // Open a handle to the directory so we can put a notification on it.
90 hr = PathGetDirectory(sczThemePath, &sczDirectory);
91 ExitOnFailure(hr, "Failed to get path directory.");
92
93 wzFileName = PathFile(sczThemePath);
94
95 hDirectory = ::CreateFileW(sczDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
96 if (INVALID_HANDLE_VALUE == hDirectory)
97 {
98 ExitWithLastError(hr, "Failed to open directory: %ls", sczDirectory);
99 }
100
101 BOOL fUpdated = FALSE;
102 do
103 {
104 // Get the last modified time on the file we're loading for verification that the
105 // file actually gets changed down below.
106 FILETIME ftModified = { };
107 FileGetTime(sczThemePath, NULL, NULL, &ftModified);
108
109 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_BEGIN, 0, 0);
110
111 // Try to load the theme file.
112 hr = ThemeLoadFromFile(sczThemePath, &pTheme);
113 if (FAILED(hr))
114 {
115 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_ERROR, 0, hr);
116 }
117 else
118 {
119 if (sczWxlPath)
120 {
121 hr = LocLoadFromFile(sczWxlPath, &pWixLoc);
122 ExitOnFailure(hr, "Failed to load loc file from path: %ls", sczWxlPath);
123
124 hr = ThemeLocalize(pTheme, pWixLoc);
125 ExitOnFailure(hr, "Failed to localize theme: %ls", sczWxlPath);
126 }
127
128 hr = AllocHandleTheme(pTheme, &pHandle);
129 ExitOnFailure(hr, "Failed to allocate handle to theme");
130
131 ::SendMessageW(hWnd, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
132 pHandle = NULL;
133 }
134
135 fUpdated = FALSE;
136 do
137 {
138 DWORD rgbNotifications[1024];
139 DWORD cbRead = 0;
140 if (!::ReadDirectoryChangesW(hDirectory, rgbNotifications, sizeof(rgbNotifications), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &cbRead, NULL, NULL))
141 {
142 ExitWithLastError(hr, "Failed while watching directory: %ls", sczDirectory);
143 }
144
145 // Wait for half a second to let all the file handles get closed to minimize access
146 // denied errors.
147 ::Sleep(500);
148
149 FILE_NOTIFY_INFORMATION* pNotification = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(rgbNotifications);
150 while (pNotification)
151 {
152 // If our file was updated, check to see if the modified time really changed. The notifications
153 // are often trigger happy thinking the file changed two or three times in a row. Maybe it's AV
154 // software creating the problems but actually checking the modified date works well.
155 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pNotification->FileName, pNotification->FileNameLength / sizeof(WCHAR), wzFileName, -1))
156 {
157 FILETIME ft = { };
158 FileGetTime(sczThemePath, NULL, NULL, &ft);
159
160 fUpdated = (ftModified.dwHighDateTime < ft.dwHighDateTime) || (ftModified.dwHighDateTime == ft.dwHighDateTime && ftModified.dwLowDateTime < ft.dwLowDateTime);
161 break;
162 }
163
164 pNotification = pNotification->NextEntryOffset ? reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(pNotification) + pNotification->NextEntryOffset) : NULL;
165 }
166 } while (!fUpdated);
167 } while(fUpdated);
168
169LExit:
170 if (fComInitialized)
171 {
172 ::CoUninitialize();
173 }
174
175 LocFree(pWixLoc);
176 ReleaseFileHandle(hDirectory);
177 ReleaseStr(sczDirectory);
178 ReleaseStr(sczThemePath);
179 ReleaseStr(sczWxlPath);
180 return hr;
181}
182
183extern "C" HRESULT AllocHandleTheme(
184 __in THEME* pTheme,
185 __out HANDLE_THEME** ppHandle
186 )
187{
188 HRESULT hr = S_OK;
189 HANDLE_THEME* pHandle = NULL;
190
191 pHandle = static_cast<HANDLE_THEME*>(MemAlloc(sizeof(HANDLE_THEME), TRUE));
192 ExitOnNull(pHandle, hr, E_OUTOFMEMORY, "Failed to allocate theme handle.");
193
194 pHandle->cReferences = 1;
195 pHandle->pTheme = pTheme;
196
197 *ppHandle = pHandle;
198 pHandle = NULL;
199
200LExit:
201 ReleaseMem(pHandle);
202 return hr;
203}
204
205extern "C" void IncrementHandleTheme(
206 __in HANDLE_THEME* pHandle
207 )
208{
209 ::InterlockedIncrement(reinterpret_cast<LONG*>(&pHandle->cReferences));
210}
211
212extern "C" void DecrementHandleTheme(
213 __in HANDLE_THEME* pHandle
214 )
215{
216 if (pHandle && 0 == ::InterlockedDecrement(reinterpret_cast<LONG*>(&pHandle->cReferences)))
217 {
218 ThemeFree(pHandle->pTheme);
219 MemFree(pHandle);
220 }
221}
diff --git a/src/tools/thmviewer/precomp.cpp b/src/tools/thmviewer/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/tools/thmviewer/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
diff --git a/src/tools/thmviewer/precomp.h b/src/tools/thmviewer/precomp.h
new file mode 100644
index 00000000..762a0623
--- /dev/null
+++ b/src/tools/thmviewer/precomp.h
@@ -0,0 +1,72 @@
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 <objbase.h>
8#include <shlobj.h>
9#include <shlwapi.h>
10#include <stdlib.h>
11#include <strsafe.h>
12
13#pragma warning(push)
14#pragma warning(disable:4458)
15#include <gdiplus.h>
16#pragma warning(pop)
17
18#include "dutil.h"
19#include "apputil.h"
20#include "memutil.h"
21#include "dictutil.h"
22#include "dirutil.h"
23#include "fileutil.h"
24#include "locutil.h"
25#include "logutil.h"
26#include "pathutil.h"
27#include "resrutil.h"
28#include "shelutil.h"
29#include "strutil.h"
30#include "thmutil.h"
31#include "wndutil.h"
32
33#include "resource.h"
34
35struct HANDLE_THEME
36{
37 DWORD cReferences;
38 THEME* pTheme;
39};
40
41enum WM_THMVWR
42{
43 WM_THMVWR_SHOWPAGE = WM_APP,
44 WM_THMVWR_PARSE_FILE,
45 WM_THMVWR_NEW_THEME,
46 WM_THMVWR_THEME_LOAD_ERROR,
47 WM_THMVWR_THEME_LOAD_BEGIN,
48};
49
50extern "C" HRESULT DisplayStart(
51 __in HINSTANCE hInstance,
52 __in HWND hWnd,
53 __out HANDLE *phThread,
54 __out DWORD* pdwThreadId
55 );
56extern "C" HRESULT LoadStart(
57 __in_z LPCWSTR wzThemePath,
58 __in_z LPCWSTR wzWxlPath,
59 __in HWND hWnd,
60 __out HANDLE* phThread
61 );
62
63extern "C" HRESULT AllocHandleTheme(
64 __in THEME* pTheme,
65 __out HANDLE_THEME** ppHandle
66 );
67extern "C" void IncrementHandleTheme(
68 __in HANDLE_THEME* pHandle
69 );
70extern "C" void DecrementHandleTheme(
71 __in HANDLE_THEME* pHandle
72 );
diff --git a/src/tools/thmviewer/resource.h b/src/tools/thmviewer/resource.h
new file mode 100644
index 00000000..4acc32cc
--- /dev/null
+++ b/src/tools/thmviewer/resource.h
@@ -0,0 +1,16 @@
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 IDC_STATIC -1
4#define THMVWR_RES_THEME_FILE 1
5#define THMVWR_RES_RICHEDIT_FILE 2
6
7// Next default values for new objects
8//
9#ifdef APSTUDIO_INVOKED
10#ifndef APSTUDIO_READONLY_SYMBOLS
11#define _APS_NEXT_RESOURCE_VALUE 102
12#define _APS_NEXT_COMMAND_VALUE 40001
13#define _APS_NEXT_CONTROL_VALUE 1003
14#define _APS_NEXT_SYMED_VALUE 101
15#endif
16#endif
diff --git a/src/tools/thmviewer/thmviewer.cpp b/src/tools/thmviewer/thmviewer.cpp
new file mode 100644
index 00000000..38f3c4dc
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.cpp
@@ -0,0 +1,564 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain";
6
7static THEME* vpTheme = NULL;
8static DWORD vdwDisplayThreadId = 0;
9static LPWSTR vsczThemeLoadErrors = NULL;
10
11enum THMVWR_CONTROL
12{
13 // Non-paged controls
14 THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID,
15};
16
17// Internal functions
18
19static HRESULT ProcessCommandLine(
20 __in_z_opt LPCWSTR wzCommandLine,
21 __out_z LPWSTR* psczThemeFile,
22 __out_z LPWSTR* psczWxlFile
23 );
24static HRESULT CreateTheme(
25 __in HINSTANCE hInstance,
26 __out THEME** ppTheme
27 );
28static HRESULT CreateMainWindowClass(
29 __in HINSTANCE hInstance,
30 __in THEME* pTheme,
31 __out ATOM* pAtom
32 );
33static LRESULT CALLBACK MainWndProc(
34 __in HWND hWnd,
35 __in UINT uMsg,
36 __in WPARAM wParam,
37 __in LPARAM lParam
38 );
39static void OnThemeLoadBegin(
40 __in_z_opt LPWSTR sczThemeLoadErrors
41 );
42static void OnThemeLoadError(
43 __in THEME* pTheme,
44 __in HRESULT hrFailure
45 );
46static void OnNewTheme(
47 __in THEME* pTheme,
48 __in HWND hWnd,
49 __in HANDLE_THEME* pHandle
50 );
51static BOOL OnThemeLoadingControl(
52 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
53 __in THEME_LOADINGCONTROL_RESULTS* pResults
54 );
55static BOOL OnThemeControlWmNotify(
56 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
57 __in THEME_CONTROLWMNOTIFY_RESULTS* pResults
58 );
59static void CALLBACK ThmviewerTraceError(
60 __in_z LPCSTR szFile,
61 __in int iLine,
62 __in REPORT_LEVEL rl,
63 __in UINT source,
64 __in HRESULT hrError,
65 __in_z __format_string LPCSTR szFormat,
66 __in va_list args
67 );
68
69
70int WINAPI wWinMain(
71 __in HINSTANCE hInstance,
72 __in_opt HINSTANCE /* hPrevInstance */,
73 __in_z LPWSTR lpCmdLine,
74 __in int /*nCmdShow*/
75 )
76{
77 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
78
79 HRESULT hr = S_OK;
80 BOOL fComInitialized = FALSE;
81 LPWSTR sczThemeFile = NULL;
82 LPWSTR sczWxlFile = NULL;
83 ATOM atom = 0;
84 HWND hWnd = NULL;
85
86 HANDLE hDisplayThread = NULL;
87 HANDLE hLoadThread = NULL;
88
89 BOOL fRet = FALSE;
90 MSG msg = { };
91
92 hr = ::CoInitialize(NULL);
93 ExitOnFailure(hr, "Failed to initialize COM.");
94 fComInitialized = TRUE;
95
96 DutilInitialize(&ThmviewerTraceError);
97
98 hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile);
99 ExitOnFailure(hr, "Failed to process command line.");
100
101 hr = CreateTheme(hInstance, &vpTheme);
102 ExitOnFailure(hr, "Failed to create theme.");
103
104 hr = CreateMainWindowClass(hInstance, vpTheme, &atom);
105 ExitOnFailure(hr, "Failed to create main window.");
106
107 hr = ThemeCreateParentWindow(vpTheme, 0, reinterpret_cast<LPCWSTR>(atom), vpTheme->sczCaption, vpTheme->dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, hInstance, NULL, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
108 ExitOnFailure(hr, "Failed to create window.");
109
110 if (!sczThemeFile)
111 {
112 // Prompt for a path to the theme file.
113 OPENFILENAMEW ofn = { };
114 WCHAR wzFile[MAX_PATH] = { };
115
116 ofn.lStructSize = sizeof(ofn);
117 ofn.hwndOwner = hWnd;
118 ofn.lpstrFile = wzFile;
119 ofn.nMaxFile = countof(wzFile);
120 ofn.lpstrFilter = L"Theme Files (*.thm)\0*.thm\0XML Files (*.xml)\0*.xml\0All Files (*.*)\0*.*\0";
121 ofn.nFilterIndex = 1;
122 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
123 ofn.lpstrTitle = vpTheme->sczCaption;
124
125 if (::GetOpenFileNameW(&ofn))
126 {
127 hr = StrAllocString(&sczThemeFile, wzFile, 0);
128 ExitOnFailure(hr, "Failed to copy opened file to theme file.");
129 }
130 else
131 {
132 ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR);
133 ExitFunction1(hr = E_INVALIDARG);
134 }
135 }
136
137 hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId);
138 ExitOnFailure(hr, "Failed to start display.");
139
140 hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread);
141 ExitOnFailure(hr, "Failed to start load.");
142
143 // message pump
144 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
145 {
146 if (-1 == fRet)
147 {
148 hr = E_UNEXPECTED;
149 ExitOnFailure(hr, "Unexpected return value from message pump.");
150 }
151 else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg))
152 {
153 ::TranslateMessage(&msg);
154 ::DispatchMessageW(&msg);
155 }
156 }
157
158LExit:
159 if (::IsWindow(hWnd))
160 {
161 ::DestroyWindow(hWnd);
162 }
163
164 if (hDisplayThread)
165 {
166 ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0);
167 ::WaitForSingleObject(hDisplayThread, 10000);
168 ::CloseHandle(hDisplayThread);
169 }
170
171 // TODO: come up with a good way to kill the load thread, probably need to switch
172 // the ReadDirectoryW() to overlapped mode.
173 ReleaseHandle(hLoadThread);
174
175 if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance))
176 {
177 DWORD er = ::GetLastError();
178 er = er;
179 }
180
181 ThemeFree(vpTheme);
182 ThemeUninitialize();
183 DutilUninitialize();
184
185 // uninitialize COM
186 if (fComInitialized)
187 {
188 ::CoUninitialize();
189 }
190
191 ReleaseNullStr(vsczThemeLoadErrors);
192 ReleaseStr(sczThemeFile);
193 ReleaseStr(sczWxlFile);
194 return hr;
195}
196
197static void CALLBACK ThmviewerTraceError(
198 __in_z LPCSTR /*szFile*/,
199 __in int /*iLine*/,
200 __in REPORT_LEVEL /*rl*/,
201 __in UINT source,
202 __in HRESULT hrError,
203 __in_z __format_string LPCSTR szFormat,
204 __in va_list args
205 )
206{
207 HRESULT hr = S_OK;
208 LPSTR sczFormattedAnsi = NULL;
209 LPWSTR sczMessage = NULL;
210
211 if (DUTIL_SOURCE_THMUTIL != source)
212 {
213 ExitFunction();
214 }
215
216 hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args);
217 ExitOnFailure(hr, "Failed to format error log string.");
218
219 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi);
220 ExitOnFailure(hr, "Failed to prepend error number to error log string.");
221
222 hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0);
223 ExitOnFailure(hr, "Failed to append theme load error.");
224
225LExit:
226 ReleaseStr(sczFormattedAnsi);
227 ReleaseStr(sczMessage);
228}
229
230
231//
232// ProcessCommandLine - process the provided command line arguments.
233//
234static HRESULT ProcessCommandLine(
235 __in_z_opt LPCWSTR wzCommandLine,
236 __out_z LPWSTR* psczThemeFile,
237 __out_z LPWSTR* psczWxlFile
238 )
239{
240 HRESULT hr = S_OK;
241 int argc = 0;
242 LPWSTR* argv = NULL;
243
244 if (wzCommandLine && *wzCommandLine)
245 {
246 hr = AppParseCommandLine(wzCommandLine, &argc, &argv);
247 ExitOnFailure(hr, "Failed to parse command line.");
248
249 for (int i = 0; i < argc; ++i)
250 {
251 if (argv[i][0] == L'-' || argv[i][0] == L'/')
252 {
253 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1))
254 {
255 if (i + 1 >= argc)
256 {
257 ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language.");
258 }
259
260 ++i;
261 }
262 }
263 else
264 {
265 LPCWSTR wzExtension = PathExtension(argv[i]);
266 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1))
267 {
268 hr = StrAllocString(psczWxlFile, argv[i], 0);
269 }
270 else
271 {
272 hr = StrAllocString(psczThemeFile, argv[i], 0);
273 }
274 ExitOnFailure(hr, "Failed to copy path to file.");
275 }
276 }
277 }
278
279LExit:
280 if (argv)
281 {
282 AppFreeCommandLineArgs(argv);
283 }
284
285 return hr;
286}
287
288static HRESULT CreateTheme(
289 __in HINSTANCE hInstance,
290 __out THEME** ppTheme
291 )
292{
293 HRESULT hr = S_OK;
294
295 hr = ThemeInitialize(hInstance);
296 ExitOnFailure(hr, "Failed to initialize theme manager.");
297
298 hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme);
299 ExitOnFailure(hr, "Failed to load theme from thmviewer.thm.");
300
301LExit:
302 return hr;
303}
304
305static HRESULT CreateMainWindowClass(
306 __in HINSTANCE hInstance,
307 __in THEME* pTheme,
308 __out ATOM* pAtom
309 )
310{
311 HRESULT hr = S_OK;
312 ATOM atom = 0;
313 WNDCLASSW wc = { };
314
315 ThemeInitializeWindowClass(pTheme, &wc, MainWndProc, hInstance, THMVWR_WINDOW_CLASS_MAIN);
316
317 atom = ::RegisterClassW(&wc);
318 if (!atom)
319 {
320 ExitWithLastError(hr, "Failed to register main windowclass .");
321 }
322
323 *pAtom = atom;
324
325LExit:
326 return hr;
327}
328
329static LRESULT CALLBACK MainWndProc(
330 __in HWND hWnd,
331 __in UINT uMsg,
332 __in WPARAM wParam,
333 __in LPARAM lParam
334 )
335{
336 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
337
338 switch (uMsg)
339 {
340 case WM_NCCREATE:
341 {
342 //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
343 //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams);
344 //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
345 }
346 break;
347
348 case WM_NCDESTROY:
349 DecrementHandleTheme(pHandleTheme);
350 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
351 ::PostQuitMessage(0);
352 break;
353
354 case WM_THMVWR_THEME_LOAD_BEGIN:
355 OnThemeLoadBegin(vsczThemeLoadErrors);
356 return 0;
357
358 case WM_THMVWR_THEME_LOAD_ERROR:
359 OnThemeLoadError(vpTheme, lParam);
360 return 0;
361
362 case WM_THMVWR_NEW_THEME:
363 OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam));
364 return 0;
365
366 case WM_THMUTIL_LOADING_CONTROL:
367 return OnThemeLoadingControl(reinterpret_cast<THEME_LOADINGCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADINGCONTROL_RESULTS*>(lParam));
368
369 case WM_THMUTIL_CONTROL_WM_NOTIFY:
370 return OnThemeControlWmNotify(reinterpret_cast<THEME_CONTROLWMNOTIFY_ARGS*>(wParam), reinterpret_cast<THEME_CONTROLWMNOTIFY_RESULTS*>(lParam));
371 }
372
373 return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam);
374}
375
376static void OnThemeLoadBegin(
377 __in_z_opt LPWSTR sczThemeLoadErrors
378 )
379{
380 ReleaseNullStr(sczThemeLoadErrors);
381}
382
383static void OnThemeLoadError(
384 __in THEME* pTheme,
385 __in HRESULT hrFailure
386 )
387{
388 HRESULT hr = S_OK;
389 LPWSTR sczMessage = NULL;
390 LPWSTR* psczErrors = NULL;
391 UINT cErrors = 0;
392 TVINSERTSTRUCTW tvi = { };
393 const THEME_CONTROL* pTreeControl = NULL;
394
395 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
396 {
397 ExitWithRootFailure(hr, E_INVALIDSTATE, "THMVWR_CONTROL_TREE control doesn't exist.");
398 }
399
400 // Add the application node.
401 tvi.hParent = NULL;
402 tvi.hInsertAfter = TVI_ROOT;
403 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
404 tvi.item.lParam = 0;
405 tvi.item.pszText = L"Failed to load theme.";
406 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
407
408 if (!vsczThemeLoadErrors)
409 {
410 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure);
411 ExitOnFailure(hr, "Failed to format error message.");
412
413 tvi.item.pszText = sczMessage;
414 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
415
416 hr = StrAllocFromError(&sczMessage, hrFailure, NULL);
417 ExitOnFailure(hr, "Failed to format error message text.");
418
419 tvi.item.pszText = sczMessage;
420 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
421 }
422 else
423 {
424 hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n");
425 ExitOnFailure(hr, "Failed to split theme load errors.");
426
427 for (DWORD i = 0; i < cErrors; ++i)
428 {
429 tvi.item.pszText = psczErrors[i];
430 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
431 }
432 }
433
434 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
435
436LExit:
437 ReleaseStr(sczMessage);
438 ReleaseMem(psczErrors);
439}
440
441
442static void OnNewTheme(
443 __in THEME* pTheme,
444 __in HWND hWnd,
445 __in HANDLE_THEME* pHandle
446 )
447{
448 const THEME_CONTROL* pTreeControl = NULL;
449 HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
450 THEME* pNewTheme = pHandle->pTheme;
451
452 WCHAR wzSelectedPage[MAX_PATH] = { };
453 HTREEITEM htiSelected = NULL;
454 TVINSERTSTRUCTW tvi = { };
455 TVITEMW item = { };
456
457 if (pOldHandle)
458 {
459 DecrementHandleTheme(pOldHandle);
460 pOldHandle = NULL;
461 }
462
463 // Pass the new theme handle to the display thread so it can get the display window prepared
464 // to show the new theme.
465 IncrementHandleTheme(pHandle);
466 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
467
468 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle));
469
470 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
471 {
472 TraceError(E_INVALIDSTATE, "Tree control doesn't exist.");
473 return;
474 }
475
476 // Remember the currently selected item by name so we can try to automatically select it later.
477 // Otherwise, the user would see their window destroyed after every save of their theme file and
478 // have to click to get the window back.
479 item.mask = TVIF_TEXT;
480 item.pszText = wzSelectedPage;
481 item.cchTextMax = countof(wzSelectedPage);
482 item.hItem = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_GETNEXTITEM, TVGN_CARET, NULL));
483 ::SendMessage(pTreeControl->hWnd, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item));
484
485 // Remove the previous items in the tree.
486 ::SendMessage(pTreeControl->hWnd, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT));
487
488 // Add the application node.
489 tvi.hParent = NULL;
490 tvi.hInsertAfter = TVI_ROOT;
491 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
492 tvi.item.lParam = 0;
493 tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window";
494
495 // Add the pages.
496 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
497 tvi.hInsertAfter = TVI_SORT;
498 for (DWORD i = 0; i < pNewTheme->cPages; ++i)
499 {
500 THEME_PAGE* pPage = pNewTheme->rgPages + i;
501 if (pPage->sczName && *pPage->sczName)
502 {
503 tvi.item.pszText = pPage->sczName;
504 tvi.item.lParam = i + 1; //prgdwPageIds[i]; - TODO: do the right thing here by calling ThemeGetPageIds(), should not assume we know how the page ids will be calculated.
505
506 HTREEITEM hti = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
507 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1))
508 {
509 htiSelected = hti;
510 }
511 }
512 }
513
514 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1))
515 {
516 htiSelected = tvi.hParent;
517 }
518
519 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
520 if (htiSelected)
521 {
522 ::SendMessage(pTreeControl->hWnd, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected));
523 }
524}
525
526static BOOL OnThemeLoadingControl(
527 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
528 __in THEME_LOADINGCONTROL_RESULTS* pResults
529 )
530{
531 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pArgs->pThemeControl->sczName, -1, L"Tree", -1))
532 {
533 pResults->wId = THMVWR_CONTROL_TREE;
534 }
535
536 pResults->hr = S_OK;
537 return TRUE;
538}
539
540static BOOL OnThemeControlWmNotify(
541 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
542 __in THEME_CONTROLWMNOTIFY_RESULTS* /*pResults*/
543 )
544{
545 BOOL fProcessed = FALSE;
546
547 switch (pArgs->lParam->code)
548 {
549 case TVN_SELCHANGEDW:
550 switch (pArgs->pThemeControl->wId)
551 {
552 case THMVWR_CONTROL_TREE:
553 NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(pArgs->lParam);
554 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam);
555 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam);
556
557 fProcessed = TRUE;
558 break;
559 }
560 break;
561 }
562
563 return fProcessed;
564}
diff --git a/src/tools/thmviewer/thmviewer.manifest b/src/tools/thmviewer/thmviewer.manifest
new file mode 100644
index 00000000..4663b61c
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.manifest
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="thmviewer.exe" version="1.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Theme Viewer</description>
8 <dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency>
9 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/></application></compatibility>
10 <application xmlns="urn:schemas-microsoft-com:asm.v3">
11 <windowsSettings xmlns="urn:schemas-microsoft-com:asm.v3">
12 <!-- pre-Win10 1607 -->
13 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
14 <!-- Win10 picks the first one it recognizes -->
15 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness>
16 </windowsSettings>
17 </application>
18 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
19</assembly>
diff --git a/src/tools/thmviewer/thmviewer.rc b/src/tools/thmviewer/thmviewer.rc
new file mode 100644
index 00000000..dc6d7242
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.rc
@@ -0,0 +1,12 @@
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 <winver.h>
4#include <windows.h>
5#include "resource.h"
6
7//#define MANIFEST_RESOURCE_ID 1
8//MANIFEST_RESOURCE_ID RT_MANIFEST "thmviewer.manifest"
9
10LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
11THMVWR_RES_THEME_FILE RCDATA "Resources\\thm.xml"
12THMVWR_RES_RICHEDIT_FILE RCDATA "Resources\\LoremIpsum.rtf"
diff --git a/src/tools/thmviewer/thmviewer.v3.ncrunchproject b/src/tools/thmviewer/thmviewer.v3.ncrunchproject
new file mode 100644
index 00000000..3cffd6ce
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.v3.ncrunchproject
@@ -0,0 +1,7 @@
1<ProjectConfiguration>
2 <Settings>
3 <AdditionalFilesToIncludeForProject>
4 <Value>thmviewer.manifest</Value>
5 </AdditionalFilesToIncludeForProject>
6 </Settings>
7</ProjectConfiguration> \ No newline at end of file
diff --git a/src/tools/thmviewer/thmviewer.vcxproj b/src/tools/thmviewer/thmviewer.vcxproj
new file mode 100644
index 00000000..3fd87878
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.vcxproj
@@ -0,0 +1,69 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup Label="ProjectConfigurations">
6 <ProjectConfiguration Include="Debug|Win32">
7 <Configuration>Debug</Configuration>
8 <Platform>Win32</Platform>
9 </ProjectConfiguration>
10 <ProjectConfiguration Include="Release|Win32">
11 <Configuration>Release</Configuration>
12 <Platform>Win32</Platform>
13 </ProjectConfiguration>
14 </ItemGroup>
15
16 <PropertyGroup Label="Globals">
17 <ProjectGuid>{95228C13-97F5-484A-B4A2-ECF4618B0881}</ProjectGuid>
18 <Keyword>Win32Proj</Keyword>
19 <ConfigurationType>Application</ConfigurationType>
20 <CharacterSet>Unicode</CharacterSet>
21 <Description>WiX Toolset Theme Viewer</Description>
22 </PropertyGroup>
23
24 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
25 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
26
27 <ImportGroup Label="ExtensionSettings">
28 </ImportGroup>
29
30 <ImportGroup Label="Shared">
31 </ImportGroup>
32
33 <PropertyGroup>
34 <ProjectAdditionalLinkLibraries>comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
35 </PropertyGroup>
36
37 <ItemGroup>
38 <ClCompile Include="display.cpp" />
39 <ClCompile Include="load.cpp" />
40 <ClCompile Include="precomp.cpp">
41 <PrecompiledHeader>Create</PrecompiledHeader>
42 </ClCompile>
43 <ClCompile Include="thmviewer.cpp" />
44 </ItemGroup>
45 <ItemGroup>
46 <ClInclude Include="precomp.h" />
47 <ClInclude Include="resource.h" />
48 </ItemGroup>
49 <ItemGroup>
50 <None Include="packages.config" />
51 <None Include="Resources\LoremIpsum.rtf" />
52 <None Include="Resources\thm.xml" />
53 </ItemGroup>
54 <ItemGroup>
55 <ResourceCompile Include="thmviewer.rc" />
56 </ItemGroup>
57 <ItemGroup>
58 <Manifest Include="thmviewer.manifest" />
59 </ItemGroup>
60
61 <ItemGroup>
62 <PackageReference Include="WixToolset.DUtil" />
63
64 <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
65 <PackageReference Include="GitInfo" PrivateAssets="All" />
66 </ItemGroup>
67
68 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
69</Project>
diff --git a/src/tools/thmviewer/thmviewer.vcxproj.filters b/src/tools/thmviewer/thmviewer.vcxproj.filters
new file mode 100644
index 00000000..488d5510
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.vcxproj.filters
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="thmviewer.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="display.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="load.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="precomp.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 </ItemGroup>
31 <ItemGroup>
32 <None Include="Resources\thm.xml">
33 <Filter>Resource Files</Filter>
34 </None>
35 <None Include="Resources\LoremIpsum.rtf">
36 <Filter>Resource Files</Filter>
37 </None>
38 <None Include="packages.config" />
39 </ItemGroup>
40 <ItemGroup>
41 <ClInclude Include="precomp.h">
42 <Filter>Header Files</Filter>
43 </ClInclude>
44 <ClInclude Include="resource.h">
45 <Filter>Header Files</Filter>
46 </ClInclude>
47 </ItemGroup>
48 <ItemGroup>
49 <ResourceCompile Include="thmviewer.rc">
50 <Filter>Resource Files</Filter>
51 </ResourceCompile>
52 </ItemGroup>
53 <ItemGroup>
54 <Manifest Include="thmviewer.manifest" />
55 </ItemGroup>
56</Project> \ No newline at end of file
diff --git a/src/tools/tools.cmd b/src/tools/tools.cmd
new file mode 100644
index 00000000..4764a8fe
--- /dev/null
+++ b/src/tools/tools.cmd
@@ -0,0 +1,18 @@
1@setlocal
2@pushd %~dp0
3
4@set _C=Debug
5:parse_args
6@if /i "%1"=="release" set _C=Release
7@if not "%1"=="" shift & goto parse_args
8
9@echo Building tools %_C%
10
11:: tools
12
13nuget restore || exit /b
14
15msbuild -t:Build -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\tools_build.binlog || exit /b
16
17@popd
18@endlocal
diff --git a/src/tools/tools.sln b/src/tools/tools.sln
new file mode 100644
index 00000000..4da1e8fd
--- /dev/null
+++ b/src/tools/tools.sln
@@ -0,0 +1,40 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio Version 16
4VisualStudioVersion = 16.0.30711.63
5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "thmviewer", "thmviewer\thmviewer.vcxproj", "{95228C13-97F5-484A-B4A2-ECF4618B0881}"
7EndProject
8Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Templates", "WixToolset.Templates\WixToolset.Templates.csproj", "{D1385232-CA10-4092-BAB5-4E5499FE144C}"
9EndProject
10Global
11 GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 Debug|Any CPU = Debug|Any CPU
13 Debug|x86 = Debug|x86
14 Release|Any CPU = Release|Any CPU
15 Release|x86 = Release|x86
16 EndGlobalSection
17 GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|Any CPU.ActiveCfg = Debug|Win32
19 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|Any CPU.Build.0 = Debug|Win32
20 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|x86.ActiveCfg = Debug|Win32
21 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Debug|x86.Build.0 = Debug|Win32
22 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|Any CPU.ActiveCfg = Release|Win32
23 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|x86.ActiveCfg = Release|Win32
24 {95228C13-97F5-484A-B4A2-ECF4618B0881}.Release|x86.Build.0 = Release|Win32
25 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|x86.ActiveCfg = Debug|Any CPU
28 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Debug|x86.Build.0 = Debug|Any CPU
29 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|Any CPU.Build.0 = Release|Any CPU
31 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|x86.ActiveCfg = Release|Any CPU
32 {D1385232-CA10-4092-BAB5-4E5499FE144C}.Release|x86.Build.0 = Release|Any CPU
33 EndGlobalSection
34 GlobalSection(SolutionProperties) = preSolution
35 HideSolutionNode = FALSE
36 EndGlobalSection
37 GlobalSection(ExtensibilityGlobals) = postSolution
38 SolutionGuid = {537F1116-39FE-4AED-A9A2-35030E5750D5}
39 EndGlobalSection
40EndGlobal