aboutsummaryrefslogtreecommitdiff
path: root/src/samples
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-05-11 07:36:37 -0700
committerRob Mensching <rob@firegiant.com>2021-05-11 07:36:37 -0700
commit3f583916719eeef598d10a5d4e14ef14f008243b (patch)
tree3d528e0ddb5c0550954217c97059d2f19cd6152a /src/samples
parent2e5ab696b8b4666d551b2a0532b95fb7fe6dbe03 (diff)
downloadwix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.gz
wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.bz2
wix-3f583916719eeef598d10a5d4e14ef14f008243b.zip
Merge Dtf
Diffstat (limited to 'src/samples')
-rw-r--r--src/samples/Dtf/DDiff/CabDiffEngine.cs131
-rw-r--r--src/samples/Dtf/DDiff/DDiff.cs72
-rw-r--r--src/samples/Dtf/DDiff/DDiff.csproj39
-rw-r--r--src/samples/Dtf/DDiff/DirectoryDiffEngine.cs154
-rw-r--r--src/samples/Dtf/DDiff/FileDiffEngine.cs83
-rw-r--r--src/samples/Dtf/DDiff/IDiffEngine.cs68
-rw-r--r--src/samples/Dtf/DDiff/MsiDiffEngine.cs276
-rw-r--r--src/samples/Dtf/DDiff/MspDiffEngine.cs127
-rw-r--r--src/samples/Dtf/DDiff/TextFileDiffEngine.cs83
-rw-r--r--src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs90
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/about.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/buildingcas.htm94
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabpack.htm63
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabs.htm101
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm63
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/caconfig.htm83
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/caproxy.htm74
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/databases.htm120
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm66
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/dependencies.htm88
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm34
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/history.htm437
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/installutil.htm94
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/inventory.htm78
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/managedcas.htm53
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/msihelper.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm80
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/packages.htm86
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/powerdiff.htm71
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/samplecas.htm84
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/samples.htm59
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/support.htm52
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/using.htm50
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/whatsnew.htm257
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/wifile.htm73
-rw-r--r--src/samples/Dtf/Documents/Guide/Content/writingcas.htm114
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhc132
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhk126
-rw-r--r--src/samples/Dtf/Documents/Guide/DTF.hhp49
-rw-r--r--src/samples/Dtf/Documents/Guide/dtfguide.helpproj29
-rw-r--r--src/samples/Dtf/Documents/Guide/styles/presentation.css394
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression.htm13
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression1.pngbin0 -> 79032 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/Compression2.pngbin0 -> 68312 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller.htm14
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller1.pngbin0 -> 207803 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller2.pngbin0 -> 180714 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/WindowsInstaller3.pngbin0 -> 120423 bytes
-rw-r--r--src/samples/Dtf/Documents/Reference/dtfref.shfbproj75
-rw-r--r--src/samples/Dtf/Documents/Reference/helplink.js184
-rw-r--r--src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj56
-rw-r--r--src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs176
-rw-r--r--src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs132
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml17
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs111
-rw-r--r--src/samples/Dtf/Inventory/Columns.resx252
-rw-r--r--src/samples/Dtf/Inventory/Features.cs107
-rw-r--r--src/samples/Dtf/Inventory/IInventoryDataProvider.cs67
-rw-r--r--src/samples/Dtf/Inventory/Inventory.cs1231
-rw-r--r--src/samples/Dtf/Inventory/Inventory.csproj42
-rw-r--r--src/samples/Dtf/Inventory/Inventory.icobin0 -> 4710 bytes
-rw-r--r--src/samples/Dtf/Inventory/Inventory.resx265
-rw-r--r--src/samples/Dtf/Inventory/components.cs626
-rw-r--r--src/samples/Dtf/Inventory/msiutils.cs46
-rw-r--r--src/samples/Dtf/Inventory/patches.cs227
-rw-r--r--src/samples/Dtf/Inventory/products.cs145
-rw-r--r--src/samples/Dtf/Inventory/xp.manifest15
-rw-r--r--src/samples/Dtf/ManagedCA/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/ManagedCA/ManagedCA.csproj33
-rw-r--r--src/samples/Dtf/ManagedCA/SampleCAs.cs127
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs711
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj33
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest20
-rw-r--r--src/samples/Dtf/Tools/MakeSfxCA/app.config10
-rw-r--r--src/samples/Dtf/Tools/SfxCA/ClrHost.cpp262
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp281
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EntryPoints.def140
-rw-r--r--src/samples/Dtf/Tools/SfxCA/EntryPoints.h162
-rw-r--r--src/samples/Dtf/Tools/SfxCA/Extract.cpp282
-rw-r--r--src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp629
-rw-r--r--src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h898
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.cpp363
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.rc10
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj68
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters62
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp209
-rw-r--r--src/samples/Dtf/Tools/SfxCA/SfxUtil.h31
-rw-r--r--src/samples/Dtf/Tools/SfxCA/packages.config4
-rw-r--r--src/samples/Dtf/Tools/SfxCA/precomp.cpp3
-rw-r--r--src/samples/Dtf/Tools/SfxCA/precomp.h18
-rw-r--r--src/samples/Dtf/Tools/Tools.proj15
-rw-r--r--src/samples/Dtf/WiFile/WiFile.cs147
-rw-r--r--src/samples/Dtf/WiFile/WiFile.csproj27
-rw-r--r--src/samples/Dtf/XPack/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/XPack/XPack.cs80
-rw-r--r--src/samples/Dtf/XPack/XPack.csproj27
97 files changed, 12813 insertions, 0 deletions
diff --git a/src/samples/Dtf/DDiff/CabDiffEngine.cs b/src/samples/Dtf/DDiff/CabDiffEngine.cs
new file mode 100644
index 00000000..6100ced8
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
10{
11 public class CabDiffEngine : IDiffEngine
12 {
13 public CabDiffEngine()
14 {
15 }
16
17 private bool IsCabinetFile(string file)
18 {
19 using(FileStream fileStream = File.OpenRead(file))
20 {
21 return new CabEngine().IsArchive(fileStream);
22 }
23 }
24
25 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
26 {
27 if(diffInput1 != null && File.Exists(diffInput1) &&
28 diffInput2 != null && File.Exists(diffInput2) &&
29 (IsCabinetFile(diffInput1) || IsCabinetFile(diffInput2)))
30 {
31 return .80f;
32 }
33 else
34 {
35 return 0;
36 }
37 }
38
39 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
40 {
41 bool difference = false;
42 IComparer caseInsComp = CaseInsensitiveComparer.Default;
43
44 // TODO: Make this faster by extracting the whole cab at once.
45 // TODO: Optimize for the match case by first comparing the whole cab files.
46
47 CabInfo cab1 = new CabInfo(diffInput1);
48 CabInfo cab2 = new CabInfo(diffInput2);
49 IList<CabFileInfo> cabFilesList1 = cab1.GetFiles();
50 IList<CabFileInfo> cabFilesList2 = cab2.GetFiles();
51 CabFileInfo[] cabFiles1 = new CabFileInfo[cabFilesList1.Count];
52 CabFileInfo[] cabFiles2 = new CabFileInfo[cabFilesList2.Count];
53 cabFilesList1.CopyTo(cabFiles1, 0);
54 cabFilesList2.CopyTo(cabFiles2, 0);
55 string[] files1 = new string[cabFiles1.Length];
56 string[] files2 = new string[cabFiles2.Length];
57 for(int i1 = 0; i1 < cabFiles1.Length; i1++) files1[i1] = cabFiles1[i1].Name;
58 for(int i2 = 0; i2 < cabFiles2.Length; i2++) files2[i2] = cabFiles2[i2].Name;
59 Array.Sort(files1, cabFiles1, caseInsComp);
60 Array.Sort(files2, cabFiles2, caseInsComp);
61
62
63 for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
64 {
65 int comp;
66 if(i1 == files1.Length)
67 {
68 comp = 1;
69 }
70 else if(i2 == files2.Length)
71 {
72 comp = -1;
73 }
74 else
75 {
76 comp = caseInsComp.Compare(files1[i1], files2[i2]);
77 }
78 if(comp < 0)
79 {
80 diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
81 i1++;
82 difference = true;
83 }
84 else if(comp > 0)
85 {
86 diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
87 i2++;
88 difference = true;
89 }
90 else
91 {
92 string tempFile1 = Path.GetTempFileName();
93 string tempFile2 = Path.GetTempFileName();
94 cabFiles1[i1].CopyTo(tempFile1, true);
95 cabFiles2[i2].CopyTo(tempFile2, true);
96 IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
97 StringWriter sw = new StringWriter();
98 if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
99 {
100 diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
101 diffOutput.Write(sw.ToString());
102 difference = true;
103 }
104
105 File.SetAttributes(tempFile1, File.GetAttributes(tempFile1) & ~FileAttributes.ReadOnly);
106 File.SetAttributes(tempFile2, File.GetAttributes(tempFile2) & ~FileAttributes.ReadOnly);
107 try
108 {
109 File.Delete(tempFile1);
110 File.Delete(tempFile2);
111 }
112 catch(IOException)
113 {
114#if DEBUG
115 Console.WriteLine("Could not delete temporary files {0} and {1}", tempFile1, tempFile2);
116#endif
117 }
118 i1++;
119 i2++;
120 }
121 }
122
123 return difference;
124 }
125
126 public virtual IDiffEngine Clone()
127 {
128 return new CabDiffEngine();
129 }
130 }
131}
diff --git a/src/samples/Dtf/DDiff/DDiff.cs b/src/samples/Dtf/DDiff/DDiff.cs
new file mode 100644
index 00000000..27a5a782
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
8{
9 public class DDiff
10 {
11 public static void Usage(TextWriter w)
12 {
13 w.WriteLine("Usage: DDiff target1 target2 [options]");
14 w.WriteLine("Example: DDiff d:\\dir1 d:\\dir2");
15 w.WriteLine("Example: DDiff patch1.msp patch2.msp /patchtarget target.msi");
16 w.WriteLine();
17 w.WriteLine("Options:");
18 w.WriteLine(" /o [filename] Output results to text file (UTF8)");
19 w.WriteLine(" /p [package.msi] Diff patches relative to target MSI");
20 }
21
22 public static int Main(string[] args)
23 {
24 if(args.Length < 2)
25 {
26 Usage(Console.Out);
27 return -1;
28 }
29
30 string input1 = args[0];
31 string input2 = args[1];
32 string[] options = new string[args.Length - 2];
33 for(int i = 0; i < options.Length; i++) options[i] = args[i+2];
34
35 TextWriter output = Console.Out;
36
37 for(int i = 0; i < options.Length - 1; i++)
38 {
39 switch(options[i].ToLower())
40 {
41 case "/o": goto case "-output";
42 case "-o": goto case "-output";
43 case "/output": goto case "-output";
44 case "-output": output = new StreamWriter(options[i+1], false, Encoding.UTF8); break;
45 }
46 }
47
48 IDiffEngineFactory diffFactory = new BestQualityDiffEngineFactory(new IDiffEngine[]
49 {
50 new DirectoryDiffEngine(),
51 new FileDiffEngine(),
52 new VersionedFileDiffEngine(),
53 new TextFileDiffEngine(),
54 new MsiDiffEngine(),
55 new CabDiffEngine(),
56 new MspDiffEngine(),
57 });
58
59 IDiffEngine diffEngine = diffFactory.GetDiffEngine(input1, input2, options);
60 if(diffEngine != null)
61 {
62 bool different = diffEngine.GetDiff(input1, input2, options, output, "", diffFactory);
63 return different ? 1 : 0;
64 }
65 else
66 {
67 Console.Error.WriteLine("Dont know how to diff those inputs.");
68 return -1;
69 }
70 }
71 }
72}
diff --git a/src/samples/Dtf/DDiff/DDiff.csproj b/src/samples/Dtf/DDiff/DDiff.csproj
new file mode 100644
index 00000000..332ad4d0
--- /dev/null
+++ b/src/samples/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.Samples.DDiff</RootNamespace>
10 <AssemblyName>DDiff</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <Compile Include="CabDiffEngine.cs" />
17 <Compile Include="DDiff.cs" />
18 <Compile Include="DirectoryDiffEngine.cs" />
19 <Compile Include="FileDiffEngine.cs" />
20 <Compile Include="IDiffEngine.cs" />
21 <Compile Include="MsiDiffEngine.cs" />
22 <Compile Include="MspDiffEngine.cs" />
23 <Compile Include="TextFileDiffEngine.cs" />
24 <Compile Include="VersionedFileDiffEngine.cs" />
25 </ItemGroup>
26
27 <ItemGroup>
28 <Reference Include="System" />
29 <Reference Include="System.Data" />
30 <Reference Include="System.Xml" />
31 <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
32 <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
33 <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
34 <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
35 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
36 </ItemGroup>
37
38 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
39</Project>
diff --git a/src/samples/Dtf/DDiff/DirectoryDiffEngine.cs b/src/samples/Dtf/DDiff/DirectoryDiffEngine.cs
new file mode 100644
index 00000000..89e8b47e
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
8{
9 public class DirectoryDiffEngine : IDiffEngine
10 {
11 public DirectoryDiffEngine()
12 {
13 }
14
15 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
16 {
17 if(diffInput1 != null && Directory.Exists(diffInput1) &&
18 diffInput2 != null && Directory.Exists(diffInput2))
19 {
20 return .70f;
21 }
22 else
23 {
24 return 0;
25 }
26 }
27
28 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
29 {
30 bool difference = false;
31 IComparer caseInsComp = CaseInsensitiveComparer.Default;
32
33 string[] files1 = Directory.GetFiles(diffInput1);
34 string[] files2 = Directory.GetFiles(diffInput2);
35 for(int i1 = 0; i1 < files1.Length; i1++)
36 {
37 files1[i1] = Path.GetFileName(files1[i1]);
38 }
39 for(int i2 = 0; i2 < files2.Length; i2++)
40 {
41 files2[i2] = Path.GetFileName(files2[i2]);
42 }
43 Array.Sort(files1, caseInsComp);
44 Array.Sort(files2, caseInsComp);
45
46 for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; )
47 {
48 int comp;
49 if(i1 == files1.Length)
50 {
51 comp = 1;
52 }
53 else if(i2 == files2.Length)
54 {
55 comp = -1;
56 }
57 else
58 {
59 comp = caseInsComp.Compare(files1[i1], files2[i2]);
60 }
61 if(comp < 0)
62 {
63 diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]);
64 i1++;
65 difference = true;
66 }
67 else if(comp > 0)
68 {
69 diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]);
70 i2++;
71 difference = true;
72 }
73 else
74 {
75 string file1 = Path.Combine(diffInput1, files1[i1]);
76 string file2 = Path.Combine(diffInput2, files2[i2]);
77 IDiffEngine diffEngine = diffFactory.GetDiffEngine(file1, file2, options);
78 StringWriter sw = new StringWriter();
79 if(diffEngine.GetDiff(file1, file2, options, sw, linePrefix + " ", diffFactory))
80 {
81 diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]);
82 diffOutput.Write(sw.ToString());
83 difference = true;
84 }
85 i1++;
86 i2++;
87 }
88 }
89
90 string[] dirs1 = Directory.GetDirectories(diffInput1);
91 string[] dirs2 = Directory.GetDirectories(diffInput2);
92 for(int i1 = 0; i1 < dirs1.Length; i1++)
93 {
94 dirs1[i1] = Path.GetFileName(dirs1[i1]);
95 }
96 for(int i2 = 0; i2 < dirs2.Length; i2++)
97 {
98 dirs2[i2] = Path.GetFileName(dirs2[i2]);
99 }
100 Array.Sort(dirs1, caseInsComp);
101 Array.Sort(dirs2, caseInsComp);
102
103 for(int i1 = 0, i2 = 0; i1 < dirs1.Length || i2 < dirs2.Length; )
104 {
105 int comp;
106 if(i1 == dirs1.Length)
107 {
108 comp = 1;
109 }
110 else if(i2 == dirs2.Length)
111 {
112 comp = -1;
113 }
114 else
115 {
116 comp = caseInsComp.Compare(dirs1[i1], dirs2[i2]);
117 }
118 if(comp < 0)
119 {
120 diffOutput.WriteLine("{0}< {1}", linePrefix, dirs1[i1]);
121 i1++;
122 difference = true;
123 }
124 else if(comp > 0)
125 {
126 diffOutput.WriteLine("{0}> {1}", linePrefix, dirs2[i2]);
127 i2++;
128 difference = true;
129 }
130 else
131 {
132 string dir1 = Path.Combine(diffInput1, dirs1[i1]);
133 string dir2 = Path.Combine(diffInput2, dirs2[i2]);
134 IDiffEngine diffEngine = diffFactory.GetDiffEngine(dir1, dir2, options);
135 StringWriter sw = new StringWriter();
136 if(diffEngine.GetDiff(dir1, dir2, options, sw, linePrefix + " ", diffFactory))
137 {
138 diffOutput.WriteLine("{0}{1}\\", linePrefix, dirs1[i1]);
139 diffOutput.Write(sw.ToString());
140 difference = true;
141 }
142 i1++;
143 i2++;
144 }
145 }
146 return difference;
147 }
148
149 public virtual IDiffEngine Clone()
150 {
151 return new DirectoryDiffEngine();
152 }
153 }
154}
diff --git a/src/samples/Dtf/DDiff/FileDiffEngine.cs b/src/samples/Dtf/DDiff/FileDiffEngine.cs
new file mode 100644
index 00000000..20ecd857
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
7{
8 public class FileDiffEngine : IDiffEngine
9 {
10 public FileDiffEngine()
11 {
12 }
13
14 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
15 {
16 if(diffInput1 != null && File.Exists(diffInput1) &&
17 diffInput2 != null && File.Exists(diffInput2))
18 {
19 return .10f;
20 }
21 else
22 {
23 return 0;
24 }
25 }
26
27 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
28 {
29 bool difference = false;
30
31 FileInfo file1 = new FileInfo(diffInput1);
32 FileInfo file2 = new FileInfo(diffInput2);
33
34 if(file1.Length != file2.Length)
35 {
36 diffOutput.WriteLine("{0}File size: {1} -> {2}", linePrefix, file1.Length, file2.Length);
37 difference = true;
38 }
39 else
40 {
41 FileStream stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
42 FileStream stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
43
44 byte[] buf1 = new byte[512];
45 byte[] buf2 = new byte[512];
46
47 while(!difference)
48 {
49 int count1 = stream1.Read(buf1, 0, buf1.Length);
50 int count2 = stream2.Read(buf2, 0, buf2.Length);
51
52 for(int i = 0; i < count1; i++)
53 {
54 if(i == count2 || buf1[i] != buf2[i])
55 {
56 difference = true;
57 break;
58 }
59 }
60 if(count1 < buf1.Length) // EOF
61 {
62 break;
63 }
64 }
65
66 stream1.Close();
67 stream2.Close();
68
69 if(difference)
70 {
71 diffOutput.WriteLine("{0}Files differ.", linePrefix);
72 }
73 }
74
75 return difference;
76 }
77
78 public virtual IDiffEngine Clone()
79 {
80 return new FileDiffEngine();
81 }
82 }
83}
diff --git a/src/samples/Dtf/DDiff/IDiffEngine.cs b/src/samples/Dtf/DDiff/IDiffEngine.cs
new file mode 100644
index 00000000..9895d6ff
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
8{
9 public interface IDiffEngine
10 {
11 float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory);
12
13 bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory);
14
15 IDiffEngine Clone();
16 }
17
18 public interface IDiffEngineFactory
19 {
20 IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options);
21 }
22
23 public class BestQualityDiffEngineFactory : IDiffEngineFactory
24 {
25 public virtual IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options)
26 {
27 float bestDiffQuality = 0;
28 IDiffEngine bestDiffEngine = null;
29
30 foreach(IDiffEngine diffEngine in diffEngines)
31 {
32 float diffQuality = diffEngine.GetDiffQuality(diffInput1, diffInput2, options, this);
33 if(diffQuality > bestDiffQuality)
34 {
35 bestDiffQuality = diffQuality;
36 bestDiffEngine = diffEngine;
37 }
38 }
39 return (bestDiffEngine != null ? bestDiffEngine.Clone() : null);
40 }
41
42 public BestQualityDiffEngineFactory() : this(null) { }
43 public BestQualityDiffEngineFactory(IDiffEngine[] diffEngines)
44 {
45 this.diffEngines = (diffEngines != null ? new ArrayList(diffEngines) : new ArrayList());
46 }
47
48 protected IList diffEngines;
49
50 public virtual void Add(IDiffEngine diffEngine)
51 {
52 diffEngines.Add(diffEngine);
53 }
54
55 public virtual void Remove(IDiffEngine diffEngine)
56 {
57 diffEngines.Remove(diffEngine);
58 }
59
60 public IList DiffEngines
61 {
62 get
63 {
64 return ArrayList.ReadOnly(diffEngines);
65 }
66 }
67 }
68}
diff --git a/src/samples/Dtf/DDiff/MsiDiffEngine.cs b/src/samples/Dtf/DDiff/MsiDiffEngine.cs
new file mode 100644
index 00000000..91bc2969
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
11{
12 public class MsiDiffEngine : IDiffEngine
13 {
14 public MsiDiffEngine()
15 {
16 }
17
18 protected bool IsMsiDatabase(string file)
19 {
20 // TODO: use something smarter?
21 switch(Path.GetExtension(file).ToLower())
22 {
23 case ".msi": return true;
24 case ".msm": return true;
25 case ".pcp": return true;
26 default : return false;
27 }
28 }
29
30 protected bool IsMspPatch(string file)
31 {
32 // TODO: use something smarter?
33 switch(Path.GetExtension(file).ToLower())
34 {
35 case ".msp": return true;
36 default : return false;
37 }
38 }
39
40 public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
41 {
42 if(diffInput1 != null && File.Exists(diffInput1) &&
43 diffInput2 != null && File.Exists(diffInput2) &&
44 (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2)))
45 {
46 return .70f;
47 }
48 else if(diffInput1 != null && File.Exists(diffInput1) &&
49 diffInput2 != null && File.Exists(diffInput2) &&
50 (IsMspPatch(diffInput1) || IsMspPatch(diffInput2)))
51 {
52 return .60f;
53 }
54 else
55 {
56 return 0;
57 }
58 }
59
60 public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
61 {
62 bool difference = false;
63 Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
64 Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
65
66 if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
67 if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
68 if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
69
70 db1.Close();
71 db2.Close();
72 return difference;
73 }
74
75 protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
76 {
77 bool difference = false;
78
79 SummaryInfo summInfo1 = db1.SummaryInfo;
80 SummaryInfo summInfo2 = db2.SummaryInfo;
81 if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; }
82 if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; }
83 if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; }
84 if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; }
85 if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; }
86 if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; }
87 if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; }
88 if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; }
89 if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; }
90 if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; }
91 if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; }
92 if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; }
93 if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; }
94 if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; }
95 if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; }
96 if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; }
97 if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; }
98 summInfo1.Close();
99 summInfo2.Close();
100
101 return difference;
102 }
103
104 protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
105 {
106 bool difference = false;
107
108 string tempFile = Path.GetTempFileName();
109 if(db2.GenerateTransform(db1, tempFile))
110 {
111 difference = true;
112
113 Database db = db1;
114 db.ViewTransform(tempFile);
115
116 string row, column, change;
117 using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " +
118 "FROM `_TransformView` ORDER BY `Table`, `Row`"))
119 {
120 view.Execute();
121
122 foreach (Record rec in view) using (rec)
123 {
124 column = String.Format("{0} {1}", rec[1], rec[2]);
125 change = "";
126 if (rec.IsNull(3))
127 {
128 row = "<DDL>";
129 if (!rec.IsNull(4))
130 {
131 change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4));
132 }
133 }
134 else
135 {
136 row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]";
137 if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE")
138 {
139 column = String.Format("{0}.{1}", rec[1], rec[2]);
140 change = "{" + rec[5] + "}->{" + rec[4] + "}";
141 }
142 }
143
144 diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change);
145 }
146 }
147 }
148 File.Delete(tempFile);
149
150 return difference;
151 }
152
153 private string DecodeColDef(int colDef)
154 {
155 const int icdLong = 0x0000;
156 const int icdShort = 0x0400;
157 const int icdObject = 0x0800;
158 const int icdString = 0x0C00;
159 const int icdTypeMask = 0x0F00;
160 const int icdNullable = 0x1000;
161 const int icdPrimaryKey = 0x2000;
162
163 string def = "";
164 switch(colDef & (icdTypeMask))
165 {
166 case icdLong : def = "LONG"; break;
167 case icdShort : def = "SHORT"; break;
168 case icdObject: def = "OBJECT"; break;
169 case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break;
170 }
171 if((colDef & icdNullable) != 0)
172 {
173 def = def + " NOT NULL";
174 }
175 if((colDef & icdPrimaryKey) != 0)
176 {
177 def = def + " PRIMARY KEY";
178 }
179 return def;
180 }
181
182 protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
183 {
184 bool difference = false;
185
186 IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
187 IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`");
188 string[] streams1 = new string[streams1List.Count];
189 string[] streams2 = new string[streams2List.Count];
190 streams1List.CopyTo(streams1, 0);
191 streams2List.CopyTo(streams2, 0);
192
193 IComparer caseInsComp = CaseInsensitiveComparer.Default;
194 Array.Sort(streams1, caseInsComp);
195 Array.Sort(streams2, caseInsComp);
196
197 for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; )
198 {
199 int comp;
200 if (i1 == streams1.Length)
201 {
202 comp = 1;
203 }
204 else if (i2 == streams2.Length)
205 {
206 comp = -1;
207 }
208 else
209 {
210 comp = caseInsComp.Compare(streams1[i1], streams2[i2]);
211 }
212 if(comp < 0)
213 {
214 diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]);
215 i1++;
216 difference = true;
217 }
218 else if(comp > 0)
219 {
220 diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]);
221 i2++;
222 difference = true;
223 }
224 else
225 {
226 if(streams1[i1] != ("" + ((char)5) + "SummaryInformation"))
227 {
228 string tempFile1 = Path.GetTempFileName();
229 string tempFile2 = Path.GetTempFileName();
230
231 using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1])))
232 {
233 view.Execute();
234
235 using (Record rec = view.Fetch())
236 {
237 rec.GetStream(1, tempFile1);
238 }
239 }
240
241 using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2])))
242 {
243 view.Execute();
244
245 using (Record rec = view.Fetch())
246 {
247 rec.GetStream(1, tempFile2);
248 }
249 }
250
251 IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options);
252 StringWriter sw = new StringWriter();
253 if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory))
254 {
255 diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]);
256 diffOutput.Write(sw.ToString());
257 difference = true;
258 }
259
260 File.Delete(tempFile1);
261 File.Delete(tempFile2);
262 }
263 i1++;
264 i2++;
265 }
266 }
267
268 return difference;
269 }
270
271 public virtual IDiffEngine Clone()
272 {
273 return new MsiDiffEngine();
274 }
275 }
276}
diff --git a/src/samples/Dtf/DDiff/MspDiffEngine.cs b/src/samples/Dtf/DDiff/MspDiffEngine.cs
new file mode 100644
index 00000000..285bc83d
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
9{
10 public class MspDiffEngine : MsiDiffEngine
11 {
12 public MspDiffEngine()
13 {
14 }
15
16 private string GetPatchTargetOption(string[] options)
17 {
18 for(int i = 0; i < options.Length - 1; i++)
19 {
20 switch(options[i].ToLower())
21 {
22 case "/p": goto case "-patchtarget";
23 case "-p": goto case "-patchtarget";
24 case "/patchtarget": goto case "-patchtarget";
25 case "-patchtarget": return options[i+1];
26 }
27 }
28 return null;
29 }
30
31 public override float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
32 {
33 if(diffInput1 != null && File.Exists(diffInput1) &&
34 diffInput2 != null && File.Exists(diffInput2) &&
35 GetPatchTargetOption(options) != null &&
36 (IsMspPatch(diffInput1) && IsMspPatch(diffInput2)))
37 {
38 return .80f;
39 }
40 else if(diffInput1 != null && File.Exists(diffInput1) &&
41 diffInput2 != null && File.Exists(diffInput2) &&
42 GetPatchTargetOption(options) == null &&
43 (IsMspPatch(diffInput1) && IsMsiDatabase(diffInput2)) ||
44 (IsMsiDatabase(diffInput1) && IsMspPatch(diffInput2)))
45 {
46 return .75f;
47 }
48 else
49 {
50 return 0;
51 }
52 }
53
54 public override bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
55 {
56 bool difference = false;
57
58 InstallPackage db1, db2;
59 if(IsMspPatch(diffInput1))
60 {
61 string patchTargetDbFile = GetPatchTargetOption(options);
62 if(patchTargetDbFile == null) patchTargetDbFile = diffInput2;
63 string tempPatchedDbFile = Path.GetTempFileName();
64 File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
65 File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
66 db1 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
67 db1.ApplyPatch(new PatchPackage(diffInput1), null);
68 db1.Commit();
69 }
70 else
71 {
72 db1 = new InstallPackage(diffInput1, DatabaseOpenMode.ReadOnly);
73 }
74 if(IsMspPatch(diffInput2))
75 {
76 string patchTargetDbFile = GetPatchTargetOption(options);
77 if(patchTargetDbFile == null) patchTargetDbFile = diffInput1;
78 string tempPatchedDbFile = Path.GetTempFileName();
79 File.Copy(patchTargetDbFile, tempPatchedDbFile, true);
80 File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly);
81 db2 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct);
82 db2.ApplyPatch(new PatchPackage(diffInput2), null);
83 db2.Commit();
84 }
85 else
86 {
87 db2 = new InstallPackage(diffInput2, DatabaseOpenMode.ReadOnly);
88 }
89
90 if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
91 if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
92 if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true;
93
94 db1.Close();
95 db2.Close();
96
97 try
98 {
99 if(IsMspPatch(diffInput1)) File.Delete(db1.FilePath);
100 if(IsMspPatch(diffInput1)) File.Delete(db2.FilePath);
101 }
102 catch(IOException)
103 {
104 #if DEBUG
105 Console.WriteLine("Could not delete temporary files {0} and {1}", db1.FilePath, db2.FilePath);
106 #endif
107 }
108
109 if(IsMspPatch(diffInput1) && IsMspPatch(diffInput2))
110 {
111 Database dbp1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly);
112 Database dbp2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly);
113
114 if(GetStreamsDiff(dbp1, dbp2, options, diffOutput, linePrefix, diffFactory)) difference = true;
115 dbp1.Close();
116 dbp2.Close();
117 }
118
119 return difference;
120 }
121
122 public override IDiffEngine Clone()
123 {
124 return new MspDiffEngine();
125 }
126 }
127}
diff --git a/src/samples/Dtf/DDiff/TextFileDiffEngine.cs b/src/samples/Dtf/DDiff/TextFileDiffEngine.cs
new file mode 100644
index 00000000..22567023
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
8{
9 public class TextFileDiffEngine : IDiffEngine
10 {
11 public TextFileDiffEngine()
12 {
13 }
14
15 private bool IsTextFile(string file)
16 {
17 // Guess whether this is a text file by reading the first few bytes and checking for non-ascii chars.
18
19 bool isText = true;
20 FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
21 byte[] buf = new byte[256];
22 int count = stream.Read(buf, 0, buf.Length);
23 for(int i = 0; i < count; i++)
24 {
25 if((buf[i] & 0x80) != 0)
26 {
27 isText = false;
28 break;
29 }
30 }
31 stream.Close();
32 return isText;
33 }
34
35 public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
36 {
37 if(diffInput1 != null && File.Exists(diffInput1) &&
38 diffInput2 != null && File.Exists(diffInput2) &&
39 (IsTextFile(diffInput1) && IsTextFile(diffInput2)))
40 {
41 return .70f;
42 }
43 else
44 {
45 return 0;
46 }
47 }
48
49 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
50 {
51 try
52 {
53 bool difference = false;
54 ProcessStartInfo psi = new ProcessStartInfo("diff.exe");
55 psi.Arguments = String.Format("\"{0}\" \"{1}\"", diffInput1, diffInput2);
56 psi.WorkingDirectory = null;
57 psi.UseShellExecute = false;
58 psi.WindowStyle = ProcessWindowStyle.Hidden;
59 psi.RedirectStandardOutput = true;
60 Process proc = Process.Start(psi);
61
62 string line;
63 while((line = proc.StandardOutput.ReadLine()) != null)
64 {
65 diffOutput.WriteLine("{0}{1}", linePrefix, line);
66 difference = true;
67 }
68
69 proc.WaitForExit();
70 return difference;
71 }
72 catch(System.ComponentModel.Win32Exception) // If diff.exe is not found, just compare the bytes
73 {
74 return new FileDiffEngine().GetDiff(diffInput1, diffInput2, options, diffOutput, linePrefix, diffFactory);
75 }
76 }
77
78 public IDiffEngine Clone()
79 {
80 return new TextFileDiffEngine();
81 }
82 }
83}
diff --git a/src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs b/src/samples/Dtf/DDiff/VersionedFileDiffEngine.cs
new file mode 100644
index 00000000..ad4014f3
--- /dev/null
+++ b/src/samples/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.Samples.DDiff
8{
9 public class VersionedFileDiffEngine : IDiffEngine
10 {
11 public VersionedFileDiffEngine()
12 {
13 }
14
15 private bool IsVersionedFile(string file)
16 {
17 return Installer.GetFileVersion(file) != "";
18 }
19
20 public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory)
21 {
22 if(diffInput1 != null && File.Exists(diffInput1) &&
23 diffInput2 != null && File.Exists(diffInput2) &&
24 (IsVersionedFile(diffInput1) || IsVersionedFile(diffInput2)))
25 {
26 return .20f;
27 }
28 else
29 {
30 return 0;
31 }
32 }
33
34 public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory)
35 {
36 bool difference = false;
37
38 string ver1 = Installer.GetFileVersion(diffInput1);
39 string ver2 = Installer.GetFileVersion(diffInput2);
40
41 if(ver1 != ver2)
42 {
43 diffOutput.WriteLine("{0}File version: {1} -> {2}", linePrefix, ver1, ver2);
44 difference = true;
45 }
46 else
47 {
48 FileStream stream1 = new FileStream(diffInput1, FileMode.Open, FileAccess.Read, FileShare.Read);
49 FileStream stream2 = new FileStream(diffInput2, FileMode.Open, FileAccess.Read, FileShare.Read);
50
51 byte[] buf1 = new byte[512];
52 byte[] buf2 = new byte[512];
53
54 while(!difference)
55 {
56 int count1 = stream1.Read(buf1, 0, buf1.Length);
57 int count2 = stream2.Read(buf2, 0, buf2.Length);
58
59 for(int i = 0; i < count1; i++)
60 {
61 if(i == count2 || buf1[i] != buf2[i])
62 {
63 difference = true;
64 break;
65 }
66 }
67 if(count1 < buf1.Length) // EOF
68 {
69 break;
70 }
71 }
72
73 stream1.Close();
74 stream2.Close();
75
76 if(difference)
77 {
78 diffOutput.WriteLine("{0}File versions match but bits differ.", linePrefix);
79 }
80 }
81
82 return difference;
83 }
84
85 public IDiffEngine Clone()
86 {
87 return new VersionedFileDiffEngine();
88 }
89 }
90}
diff --git a/src/samples/Dtf/Documents/Guide/Content/about.htm b/src/samples/Dtf/Documents/Guide/Content/about.htm
new file mode 100644
index 00000000..393b5a81
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/buildingcas.htm b/src/samples/Dtf/Documents/Guide/Content/buildingcas.htm
new file mode 100644
index 00000000..e88ad552
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/cabpack.htm b/src/samples/Dtf/Documents/Guide/Content/cabpack.htm
new file mode 100644
index 00000000..2d9f725e
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/cabs.htm b/src/samples/Dtf/Documents/Guide/Content/cabs.htm
new file mode 100644
index 00000000..e88d1e15
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/cabwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/cabwrapper.htm
new file mode 100644
index 00000000..fd88437c
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/caconfig.htm b/src/samples/Dtf/Documents/Guide/Content/caconfig.htm
new file mode 100644
index 00000000..a6c97d2b
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/caproxy.htm b/src/samples/Dtf/Documents/Guide/Content/caproxy.htm
new file mode 100644
index 00000000..2ee962d5
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/databases.htm b/src/samples/Dtf/Documents/Guide/Content/databases.htm
new file mode 100644
index 00000000..4fe1fba9
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/debuggingcas.htm b/src/samples/Dtf/Documents/Guide/Content/debuggingcas.htm
new file mode 100644
index 00000000..ca1be161
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/dependencies.htm b/src/samples/Dtf/Documents/Guide/Content/dependencies.htm
new file mode 100644
index 00000000..cfec5880
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/filepatchwrapper.htm
new file mode 100644
index 00000000..6bab69b5
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/history.htm b/src/samples/Dtf/Documents/Guide/Content/history.htm
new file mode 100644
index 00000000..704ce875
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/installutil.htm b/src/samples/Dtf/Documents/Guide/Content/installutil.htm
new file mode 100644
index 00000000..e235a7b6
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/inventory.htm b/src/samples/Dtf/Documents/Guide/Content/inventory.htm
new file mode 100644
index 00000000..40a6ef74
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/managedcas.htm b/src/samples/Dtf/Documents/Guide/Content/managedcas.htm
new file mode 100644
index 00000000..9cce0432
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/msihelper.htm b/src/samples/Dtf/Documents/Guide/Content/msihelper.htm
new file mode 100644
index 00000000..c1493117
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/msiwrapper.htm b/src/samples/Dtf/Documents/Guide/Content/msiwrapper.htm
new file mode 100644
index 00000000..70190ac4
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/packages.htm b/src/samples/Dtf/Documents/Guide/Content/packages.htm
new file mode 100644
index 00000000..aa521685
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/powerdiff.htm b/src/samples/Dtf/Documents/Guide/Content/powerdiff.htm
new file mode 100644
index 00000000..f420b47e
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/samplecas.htm b/src/samples/Dtf/Documents/Guide/Content/samplecas.htm
new file mode 100644
index 00000000..4dfed6f0
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/samples.htm b/src/samples/Dtf/Documents/Guide/Content/samples.htm
new file mode 100644
index 00000000..3bcd379a
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/support.htm b/src/samples/Dtf/Documents/Guide/Content/support.htm
new file mode 100644
index 00000000..89acbadf
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/using.htm b/src/samples/Dtf/Documents/Guide/Content/using.htm
new file mode 100644
index 00000000..6fe960e8
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/whatsnew.htm b/src/samples/Dtf/Documents/Guide/Content/whatsnew.htm
new file mode 100644
index 00000000..3efe67bd
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/wifile.htm b/src/samples/Dtf/Documents/Guide/Content/wifile.htm
new file mode 100644
index 00000000..20998b73
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/Content/writingcas.htm b/src/samples/Dtf/Documents/Guide/Content/writingcas.htm
new file mode 100644
index 00000000..6beccf5f
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/DTF.hhc b/src/samples/Dtf/Documents/Guide/DTF.hhc
new file mode 100644
index 00000000..bf43e447
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/DTF.hhk b/src/samples/Dtf/Documents/Guide/DTF.hhk
new file mode 100644
index 00000000..bc6e49b3
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/DTF.hhp b/src/samples/Dtf/Documents/Guide/DTF.hhp
new file mode 100644
index 00000000..e9b8ad90
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/dtfguide.helpproj b/src/samples/Dtf/Documents/Guide/dtfguide.helpproj
new file mode 100644
index 00000000..4df2765d
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Guide/styles/presentation.css b/src/samples/Dtf/Documents/Guide/styles/presentation.css
new file mode 100644
index 00000000..b71c8582
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Reference/Compression.htm b/src/samples/Dtf/Documents/Reference/Compression.htm
new file mode 100644
index 00000000..7782bea1
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Reference/Compression1.png b/src/samples/Dtf/Documents/Reference/Compression1.png
new file mode 100644
index 00000000..5b2e177f
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/Compression1.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/Compression2.png b/src/samples/Dtf/Documents/Reference/Compression2.png
new file mode 100644
index 00000000..394a5f18
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/Compression2.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller.htm b/src/samples/Dtf/Documents/Reference/WindowsInstaller.htm
new file mode 100644
index 00000000..28990ce4
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Reference/WindowsInstaller1.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png
new file mode 100644
index 00000000..cc769cc7
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller1.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png
new file mode 100644
index 00000000..0c11e501
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller2.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png b/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png
new file mode 100644
index 00000000..68acd7d8
--- /dev/null
+++ b/src/samples/Dtf/Documents/Reference/WindowsInstaller3.png
Binary files differ
diff --git a/src/samples/Dtf/Documents/Reference/dtfref.shfbproj b/src/samples/Dtf/Documents/Reference/dtfref.shfbproj
new file mode 100644
index 00000000..e45d2a07
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Documents/Reference/helplink.js b/src/samples/Dtf/Documents/Reference/helplink.js
new file mode 100644
index 00000000..a4989824
--- /dev/null
+++ b/src/samples/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/samples/Dtf/EmbeddedUI/AssemblyInfo.cs b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs
new file mode 100644
index 00000000..7a2fa039
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/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("Sample managed embedded external UI")]
diff --git a/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
new file mode 100644
index 00000000..e4c52a26
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 <PropertyGroup>
5 <ProjectGuid>{864B8C50-7895-4485-AC89-900D86FD8C0D}</ProjectGuid>
6 <OutputType>Library</OutputType>
7 <RootNamespace>WixToolset.Dtf.Samples.EmbeddedUI</RootNamespace>
8 <AssemblyName>WixToolset.Dtf.Samples.EmbeddedUI</AssemblyName>
9 <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
10 <FileAlignment>512</FileAlignment>
11 </PropertyGroup>
12 <ItemGroup>
13 <Compile Include="AssemblyInfo.cs" />
14 <Compile Include="InstallProgressCounter.cs" />
15 <Compile Include="SampleEmbeddedUI.cs" />
16 <Compile Include="SetupWizard.xaml.cs">
17 <DependentUpon>SetupWizard.xaml</DependentUpon>
18 </Compile>
19 </ItemGroup>
20 <ItemGroup>
21 <Page Include="SetupWizard.xaml">
22 <Generator>MSBuild:Compile</Generator>
23 <SubType>Designer</SubType>
24 </Page>
25 </ItemGroup>
26 <ItemGroup>
27 <Reference Include="PresentationCore">
28 <RequiredTargetFramework>3.0</RequiredTargetFramework>
29 </Reference>
30 <Reference Include="PresentationFramework">
31 <RequiredTargetFramework>3.0</RequiredTargetFramework>
32 </Reference>
33 <Reference Include="System" />
34 <Reference Include="System.Core">
35 <RequiredTargetFramework>3.5</RequiredTargetFramework>
36 </Reference>
37 <Reference Include="System.Xml" />
38 <Reference Include="WindowsBase">
39 <RequiredTargetFramework>3.0</RequiredTargetFramework>
40 </Reference>
41 </ItemGroup>
42 <ItemGroup>
43 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
44 <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
45 <Name>WixToolset.Dtf.WindowsInstaller</Name>
46 </ProjectReference>
47 </ItemGroup>
48
49 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
50<!--
51 <PropertyGroup>
52 <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" "$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll"</PostBuildEvent>
53 </PropertyGroup>
54-->
55
56</Project>
diff --git a/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
new file mode 100644
index 00000000..df77e106
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
@@ -0,0 +1,176 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using WixToolset.Dtf.WindowsInstaller;
7
8 /// <summary>
9 /// Tracks MSI progress messages and converts them to usable progress.
10 /// </summary>
11 public class InstallProgressCounter
12 {
13 private int total;
14 private int completed;
15 private int step;
16 private bool moveForward;
17 private bool enableActionData;
18 private int progressPhase;
19 private double scriptPhaseWeight;
20
21 public InstallProgressCounter() : this(0.3)
22 {
23 }
24
25 public InstallProgressCounter(double scriptPhaseWeight)
26 {
27 if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1))
28 {
29 throw new ArgumentOutOfRangeException("scriptPhaseWeight");
30 }
31
32 this.scriptPhaseWeight = scriptPhaseWeight;
33 }
34
35 /// <summary>
36 /// Gets a number between 0 and 1 that indicates the overall installation progress.
37 /// </summary>
38 public double Progress { get; private set; }
39
40 public void ProcessMessage(InstallMessage messageType, Record messageRecord)
41 {
42 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
43
44 switch (messageType)
45 {
46 case InstallMessage.ActionStart:
47 if (this.enableActionData)
48 {
49 this.enableActionData = false;
50 }
51 break;
52
53 case InstallMessage.ActionData:
54 if (this.enableActionData)
55 {
56 if (this.moveForward)
57 {
58 this.completed += this.step;
59 }
60 else
61 {
62 this.completed -= this.step;
63 }
64
65 this.UpdateProgress();
66 }
67 break;
68
69 case InstallMessage.Progress:
70 this.ProcessProgressMessage(messageRecord);
71 break;
72 }
73 }
74
75 private void ProcessProgressMessage(Record progressRecord)
76 {
77 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
78
79 if (progressRecord == null || progressRecord.FieldCount == 0)
80 {
81 return;
82 }
83
84 int fieldCount = progressRecord.FieldCount;
85 int progressType = progressRecord.GetInteger(1);
86 string progressTypeString = String.Empty;
87 switch (progressType)
88 {
89 case 0: // Master progress reset
90 if (fieldCount < 4)
91 {
92 return;
93 }
94
95 this.progressPhase++;
96
97 this.total = progressRecord.GetInteger(2);
98 if (this.progressPhase == 1)
99 {
100 // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
101 // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
102 // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
103 // "close" and deal with the rest.
104 this.total += 50;
105 }
106
107 this.moveForward = (progressRecord.GetInteger(3) == 0);
108 this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max
109 this.enableActionData = false;
110
111 this.UpdateProgress();
112 break;
113
114 case 1: // Action info
115 if (fieldCount < 3)
116 {
117 return;
118 }
119
120 if (progressRecord.GetInteger(3) == 0)
121 {
122 this.enableActionData = false;
123 }
124 else
125 {
126 this.enableActionData = true;
127 this.step = progressRecord.GetInteger(2);
128 }
129 break;
130
131 case 2: // Progress report
132 if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0)
133 {
134 return;
135 }
136
137 if (this.moveForward)
138 {
139 this.completed += progressRecord.GetInteger(2);
140 }
141 else
142 {
143 this.completed -= progressRecord.GetInteger(2);
144 }
145
146 this.UpdateProgress();
147 break;
148
149 case 3: // Progress total addition
150 this.total += progressRecord.GetInteger(2);
151 break;
152 }
153 }
154
155 private void UpdateProgress()
156 {
157 if (this.progressPhase < 1 || this.total == 0)
158 {
159 this.Progress = 0;
160 }
161 else if (this.progressPhase == 1)
162 {
163 this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total;
164 }
165 else if (this.progressPhase == 2)
166 {
167 this.Progress = this.scriptPhaseWeight +
168 (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total;
169 }
170 else
171 {
172 this.Progress = 1;
173 }
174 }
175 }
176}
diff --git a/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
new file mode 100644
index 00000000..9b26bef5
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
@@ -0,0 +1,132 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Configuration;
8 using System.Threading;
9 using System.Windows;
10 using System.Windows.Threading;
11 using WixToolset.Dtf.WindowsInstaller;
12 using Application = System.Windows.Application;
13
14 public class SampleEmbeddedUI : IEmbeddedUI
15 {
16 private Thread appThread;
17 private Application app;
18 private SetupWizard setupWizard;
19 private ManualResetEvent installStartEvent;
20 private ManualResetEvent installExitEvent;
21
22 /// <summary>
23 /// Initializes the embedded UI.
24 /// </summary>
25 /// <param name="session">Handle to the installer which can be used to get and set properties.
26 /// The handle is only valid for the duration of this method call.</param>
27 /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
28 /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
29 /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
30 /// <returns>True if the embedded UI was successfully initialized; false if the installation
31 /// should continue without the embedded UI.</returns>
32 /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
33 /// <exception cref="InstallerException">The embedded UI failed to initialize and
34 /// causes the installation to fail.</exception>
35 public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel)
36 {
37 if (session != null)
38 {
39 if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full)
40 {
41 // Don't show custom UI when the UI level is set to basic.
42 return false;
43
44 // An embedded UI could display an alternate dialog sequence for reduced or
45 // basic modes, but it's not implemented here. We'll just fall back to the
46 // built-in MSI basic UI.
47 }
48
49 if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase))
50 {
51 // Don't show custom UI when uninstalling.
52 return false;
53
54 // An embedded UI could display an uninstall wizard, it's just not imlemented here.
55 }
56 }
57
58 // Start the setup wizard on a separate thread.
59 this.installStartEvent = new ManualResetEvent(false);
60 this.installExitEvent = new ManualResetEvent(false);
61 this.appThread = new Thread(this.Run);
62 this.appThread.SetApartmentState(ApartmentState.STA);
63 this.appThread.Start();
64
65 // Wait for the setup wizard to either kickoff the install or prematurely exit.
66 int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent });
67 if (waitResult == 1)
68 {
69 // The setup wizard set the exit event instead of the start event. Cancel the installation.
70 throw new InstallCanceledException();
71 }
72 else
73 {
74 // Start the installation with a silenced internal UI.
75 // This "embedded external UI" will handle message types except for source resolution.
76 internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly;
77 return true;
78 }
79 }
80
81 /// <summary>
82 /// Processes information and progress messages sent to the user interface.
83 /// </summary>
84 /// <param name="messageType">Message type.</param>
85 /// <param name="messageRecord">Record that contains message data.</param>
86 /// <param name="buttons">Message box buttons.</param>
87 /// <param name="icon">Message box icon.</param>
88 /// <param name="defaultButton">Message box default button.</param>
89 /// <returns>Result of processing the message.</returns>
90 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
91 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
92 {
93 // Synchronously send the message to the setup wizard window on its thread.
94 object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send,
95 new Func<MessageResult>(delegate()
96 {
97 return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton);
98 }));
99 return (MessageResult) result;
100 }
101
102 /// <summary>
103 /// Shuts down the embedded UI at the end of the installation.
104 /// </summary>
105 /// <remarks>
106 /// If the installation was canceled during initialization, this method will not be called.
107 /// If the installation was canceled or failed at any later point, this method will be called at the end.
108 /// </remarks>
109 public void Shutdown()
110 {
111 // Wait for the user to exit the setup wizard.
112 this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
113 new Action(delegate()
114 {
115 this.setupWizard.EnableExit();
116 }));
117 this.appThread.Join();
118 }
119
120 /// <summary>
121 /// Creates the setup wizard and runs the application thread.
122 /// </summary>
123 private void Run()
124 {
125 this.app = new Application();
126 this.setupWizard = new SetupWizard(this.installStartEvent);
127 this.setupWizard.InitializeComponent();
128 this.app.Run(this.setupWizard);
129 this.installExitEvent.Set();
130 }
131 }
132}
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
new file mode 100644
index 00000000..a43059e8
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
@@ -0,0 +1,17 @@
1
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Window x:Class="WixToolset.Dtf.Samples.EmbeddedUI.SetupWizard"
6 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8 Title="Sample Embedded UI" Height="400" Width="540" Visibility="Visible">
9 <Grid>
10 <TextBox Margin="8,8,8,63" Name="messagesTextBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" FontFamily="Lucida Console" FontSize="10" />
11 <Button Height="23" HorizontalAlignment="Right" Name="installButton" VerticalAlignment="Bottom" Width="75" Click="installButton_Click" Margin="0,0,91,8">Install</Button>
12 <Button Height="23" HorizontalAlignment="Right" Name="exitButton" VerticalAlignment="Bottom" Width="75" Visibility="Hidden" Click="exitButton_Click" Margin="0,0,8,8">Exit</Button>
13 <Button Height="23" Margin="0,0,8,8" Name="cancelButton" VerticalAlignment="Bottom" Width="75" HorizontalAlignment="Right" Click="cancelButton_Click">Cancel</Button>
14 <ProgressBar Height="16" Margin="8,0,8,39" Name="progressBar" VerticalAlignment="Bottom" Visibility="Hidden" IsIndeterminate="False" />
15 <Label Height="28" HorizontalAlignment="Left" Margin="8,0,0,4.48" Name="progressLabel" VerticalAlignment="Bottom" Width="120" Visibility="Hidden">0%</Label>
16 </Grid>
17</Window>
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
new file mode 100644
index 00000000..b25b8a9e
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
@@ -0,0 +1,111 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Threading;
10 using System.Windows;
11 using System.Windows.Controls;
12 using System.Windows.Data;
13 using System.Windows.Documents;
14 using System.Windows.Input;
15 using System.Windows.Media;
16 using System.Windows.Media.Imaging;
17 using System.Windows.Navigation;
18 using System.Windows.Shapes;
19 using WixToolset.Dtf.WindowsInstaller;
20
21 /// <summary>
22 /// Interaction logic for SetupWizard.xaml
23 /// </summary>
24 public partial class SetupWizard : Window
25 {
26 private ManualResetEvent installStartEvent;
27 private InstallProgressCounter progressCounter;
28 private bool canceled;
29
30 public SetupWizard(ManualResetEvent installStartEvent)
31 {
32 this.installStartEvent = installStartEvent;
33 this.progressCounter = new InstallProgressCounter(0.5);
34 }
35
36 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
37 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
38 {
39 try
40 {
41 this.progressCounter.ProcessMessage(messageType, messageRecord);
42 this.progressBar.Value = this.progressBar.Minimum +
43 this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum);
44 this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%";
45
46 switch (messageType)
47 {
48 case InstallMessage.Error:
49 case InstallMessage.Warning:
50 case InstallMessage.Info:
51 string message = String.Format("{0}: {1}", messageType, messageRecord);
52 this.LogMessage(message);
53 break;
54 }
55
56 if (this.canceled)
57 {
58 this.canceled = false;
59 return MessageResult.Cancel;
60 }
61 }
62 catch (Exception ex)
63 {
64 this.LogMessage(ex.ToString());
65 this.LogMessage(ex.StackTrace);
66 }
67
68 return MessageResult.OK;
69 }
70
71 private void LogMessage(string message)
72 {
73 this.messagesTextBox.Text += Environment.NewLine + message;
74 this.messagesTextBox.ScrollToEnd();
75 }
76
77 internal void EnableExit()
78 {
79 this.progressBar.Visibility = Visibility.Hidden;
80 this.progressLabel.Visibility = Visibility.Hidden;
81 this.cancelButton.Visibility = Visibility.Hidden;
82 this.exitButton.Visibility = Visibility.Visible;
83 }
84
85 private void installButton_Click(object sender, RoutedEventArgs e)
86 {
87 this.installButton.Visibility = Visibility.Hidden;
88 this.progressBar.Visibility = Visibility.Visible;
89 this.progressLabel.Visibility = Visibility.Visible;
90 this.installStartEvent.Set();
91 }
92
93 private void exitButton_Click(object sender, RoutedEventArgs e)
94 {
95 this.Close();
96 }
97
98 private void cancelButton_Click(object sender, RoutedEventArgs e)
99 {
100 if (this.installButton.Visibility == Visibility.Visible)
101 {
102 this.Close();
103 }
104 else
105 {
106 this.canceled = true;
107 this.cancelButton.IsEnabled = false;
108 }
109 }
110 }
111}
diff --git a/src/samples/Dtf/Inventory/Columns.resx b/src/samples/Dtf/Inventory/Columns.resx
new file mode 100644
index 00000000..cfeb11e3
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Inventory/Features.cs b/src/samples/Dtf/Inventory/Features.cs
new file mode 100644
index 00000000..c114da86
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about features of products installed on the system.
16 /// </summary>
17 public class FeaturesInventory : IInventoryDataProvider
18 {
19 private static object syncRoot = new object();
20
21 public FeaturesInventory()
22 {
23 }
24
25 public string Description
26 {
27 get { return "Features of installed products"; }
28 }
29
30 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
31 {
32 statusCallback(0, @"Products\...\Features");
33 ArrayList nodes = new ArrayList();
34 foreach (ProductInstallation product in ProductInstallation.AllProducts)
35 {
36 nodes.Add(String.Format(@"Products\{0}\Features", MsiUtils.GetProductName(product.ProductCode)));
37 }
38 statusCallback(nodes.Count, String.Empty);
39 return (string[]) nodes.ToArray(typeof(string));
40 }
41
42 public bool IsNodeSearchable(string searchRoot, string searchNode)
43 {
44 return true;
45 }
46
47 public DataView GetData(string nodePath)
48 {
49 string[] path = nodePath.Split('\\');
50
51 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
52 {
53 return GetProductFeaturesData(MsiUtils.GetProductCode(path[1]));
54 }
55 return null;
56 }
57
58 public DataView GetProductFeaturesData(string productCode)
59 {
60 DataTable table = new DataTable("ProductFeatures");
61 table.Locale = CultureInfo.InvariantCulture;
62 table.Columns.Add("ProductFeaturesFeatureTitle", typeof(string));
63 table.Columns.Add("ProductFeaturesFeatureName", typeof(string));
64 table.Columns.Add("ProductFeaturesInstallState", typeof(string));
65
66 try
67 {
68 IntPtr hWnd = IntPtr.Zero;
69 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
70 lock(syncRoot) // Only one Installer session can be active at a time
71 {
72 using(Session session = Installer.OpenProduct(productCode))
73 {
74 session.DoAction("CostInitialize");
75 session.DoAction("FileCost");
76 session.DoAction("CostFinalize");
77
78 IList<string> featuresAndTitles = session.Database.ExecuteStringQuery(
79 "SELECT `Title`, `Feature` FROM `Feature`");
80
81 for(int i = 0; i < featuresAndTitles.Count; i += 2)
82 {
83 InstallState featureState = session.Features[featuresAndTitles[i + 1]].CurrentState;
84 table.Rows.Add(new object[] { featuresAndTitles[i], featuresAndTitles[i+1],
85 (featureState == InstallState.Advertised ? "Advertised" : featureState.ToString()) });
86 }
87 }
88 }
89 return new DataView(table, "", "ProductFeaturesFeatureTitle ASC", DataViewRowState.CurrentRows);
90 }
91 catch(InstallerException) { }
92 catch(IOException) { }
93 return null;
94 }
95
96 public string GetLink(string nodePath, DataRow row)
97 {
98 string[] path = nodePath.Split('\\');
99
100 if(path.Length == 3 && path[0] == "Products" && path[2] == "Features")
101 {
102 return String.Format(@"Products\{0}\Features\{1}", path[1], row["ProductFeaturesFeatureName"]);
103 }
104 return null;
105 }
106 }
107}
diff --git a/src/samples/Dtf/Inventory/IInventoryDataProvider.cs b/src/samples/Dtf/Inventory/IInventoryDataProvider.cs
new file mode 100644
index 00000000..23f2c187
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
7{
8 /// <summary>
9 /// Reports the total number of items loaded so far by <see cref="IInventoryDataProvider.GetNodes"/>.
10 /// </summary>
11 public delegate void InventoryDataLoadStatusCallback(int itemsLoaded, string currentNode);
12
13 /// <summary>
14 /// Inventory data providers implement this interface to provide a particular type of data.
15 /// Implementors must provide a parameterless constructor.
16 /// </summary>
17 public interface IInventoryDataProvider
18 {
19 /// <summary>
20 /// Gets a description of the data provided. This description allows
21 /// the user to choose what type of data to gather.
22 /// </summary>
23 string Description { get; }
24
25 /// <summary>
26 /// Gets the paths of all nodes for which this object provides data.
27 /// </summary>
28 /// <param name="statusCallback">Callback for reporting status.
29 /// The callback should not necessarily be invoked for every individual
30 /// node loaded, rather only every significant chunk.</param>
31 /// <returns>An array of node paths. The parts of the node paths
32 /// are delimited by backslashes (\).</returns>
33 string[] GetNodes(InventoryDataLoadStatusCallback statusCallback);
34
35 /// <summary>
36 /// When related nodes of a tree consist of duplicate data, it's
37 /// inefficient to search them all. This method indicates which
38 /// nodes should be search and which should be ignored.
39 /// </summary>
40 /// <param name="searchRoot">Root node of the subtree-search.</param>
41 /// <param name="searchNode">Node which may or may not be searched.</param>
42 /// <returns>True if the node should be searched, false otherwise.</returns>
43 bool IsNodeSearchable(string searchRoot, string searchNode);
44
45 /// <summary>
46 /// Gets the data for a particular node.
47 /// </summary>
48 /// <param name="nodePath">Path of the node for which data is requested.
49 /// This is one of the paths returned by <see cref="GetNodes"/>.</param>
50 /// <returns>DataView of a table filled with data, or null if data is
51 /// not available.</returns>
52 DataView GetData(string nodePath);
53
54 /// <summary>
55 /// Gets the path of another node which provides more details about
56 /// a particular data row.
57 /// </summary>
58 /// <param name="nodePath">Path of the node containing the data
59 /// row being queried.</param>
60 /// <param name="row">Data row being queried.</param>
61 /// <returns>Path to another node. This is not necessarily
62 /// one of the nodes returned by <see cref="GetNodes"/>. If the
63 /// node path is unknown, it will be ignored. This method may
64 /// return null if there is no detail node for the row.</returns>
65 string GetLink(string nodePath, DataRow row);
66 }
67}
diff --git a/src/samples/Dtf/Inventory/Inventory.cs b/src/samples/Dtf/Inventory/Inventory.cs
new file mode 100644
index 00000000..02793be8
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
26{
27 public class Inventory : System.Windows.Forms.Form
28 {
29 [STAThread]
30 public static void Main()
31 {
32 if (WixToolset.Dtf.WindowsInstaller.Installer.Version < new Version(3, 0))
33 {
34 MessageBox.Show("This application requires Windows Installer version 3.0 or later.",
35 "Inventory", MessageBoxButtons.OK, MessageBoxIcon.Error);
36 return;
37 }
38
39 Application.Run(new Inventory());
40 }
41
42 private IInventoryDataProvider[] dataProviders;
43 private Hashtable dataProviderMap;
44 private Hashtable data;
45 private ArrayList tablesLoading;
46 private bool searching;
47 private bool stopSearch;
48 private bool navigating;
49 private string continueSearchRoot;
50 private string continueSearchPath;
51 private DataGridCell continueSearchCell;
52 private DataGridCell continueSearchEndCell;
53 private bool mouseOverGridLink = false;
54 private Stack historyBack;
55 private Stack historyForward;
56 private Stack cellHistoryBack;
57 private Stack cellHistoryForward;
58 private static readonly DataGridCell anyCell = new DataGridCell(-1,-1);
59 private static readonly DataGridCell zeroCell = new DataGridCell(0,0);
60 private static object syncRoot = new object();
61
62 private System.Windows.Forms.DataGrid dataGrid;
63 private System.Windows.Forms.TreeView treeView;
64 private System.Windows.Forms.Panel toolPanel;
65 private System.Windows.Forms.Splitter splitter;
66 private System.Windows.Forms.Panel dataPanel;
67 private System.Windows.Forms.Button backButton;
68 private System.Windows.Forms.Button forwardButton;
69 private System.Windows.Forms.Button findButton;
70 private System.Windows.Forms.TextBox findTextBox;
71 private System.Windows.Forms.Button refreshButton;
72 private System.Windows.Forms.Button findStopButton;
73 private System.Windows.Forms.CheckBox searchTreeCheckBox;
74 private System.Windows.Forms.ToolTip gridLinkTip;
75 private System.ComponentModel.IContainer components;
76
77 public Inventory()
78 {
79 InitializeComponent();
80
81 this.gridLinkTip.InitialDelay = 0;
82 this.gridLinkTip.ReshowDelay = 0;
83
84 this.dataProviderMap = new Hashtable();
85 this.data = new Hashtable();
86 this.tablesLoading = new ArrayList();
87 this.historyBack = new Stack();
88 this.historyForward = new Stack();
89 this.cellHistoryBack = new Stack();
90 this.cellHistoryForward = new Stack();
91 }
92
93 protected override void Dispose(bool disposing)
94 {
95 if(disposing)
96 {
97 if(components != null)
98 {
99 components.Dispose();
100 }
101 }
102 base.Dispose(disposing);
103 }
104
105 #region Windows Form Designer generated code
106 /// <summary>
107 /// Required method for Designer support - do not modify
108 /// the contents of this method with the code editor.
109 /// </summary>
110 private void InitializeComponent()
111 {
112 this.components = new System.ComponentModel.Container();
113 this.dataGrid = new System.Windows.Forms.DataGrid();
114 this.treeView = new System.Windows.Forms.TreeView();
115 this.toolPanel = new System.Windows.Forms.Panel();
116 this.findStopButton = new System.Windows.Forms.Button();
117 this.findButton = new System.Windows.Forms.Button();
118 this.searchTreeCheckBox = new System.Windows.Forms.CheckBox();
119 this.findTextBox = new System.Windows.Forms.TextBox();
120 this.refreshButton = new System.Windows.Forms.Button();
121 this.forwardButton = new System.Windows.Forms.Button();
122 this.backButton = new System.Windows.Forms.Button();
123 this.dataPanel = new System.Windows.Forms.Panel();
124 this.splitter = new System.Windows.Forms.Splitter();
125 this.gridLinkTip = new System.Windows.Forms.ToolTip(this.components);
126 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).BeginInit();
127 this.toolPanel.SuspendLayout();
128 this.dataPanel.SuspendLayout();
129 this.SuspendLayout();
130 //
131 // dataGrid
132 //
133 this.dataGrid.DataMember = "";
134 this.dataGrid.Dock = System.Windows.Forms.DockStyle.Fill;
135 this.dataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
136 this.dataGrid.Location = new System.Drawing.Point(230, 0);
137 this.dataGrid.Name = "dataGrid";
138 this.dataGrid.ReadOnly = true;
139 this.dataGrid.SelectionBackColor = System.Drawing.SystemColors.Highlight;
140 this.dataGrid.Size = new System.Drawing.Size(562, 432);
141 this.dataGrid.TabIndex = 1;
142 this.dataGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyDown);
143 this.dataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseDown);
144 this.dataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyUp);
145 this.dataGrid.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseMove);
146 this.dataGrid.MouseLeave += new System.EventHandler(this.dataGrid_MouseLeave);
147 //
148 // treeView
149 //
150 this.treeView.Dock = System.Windows.Forms.DockStyle.Left;
151 this.treeView.HideSelection = false;
152 this.treeView.ImageIndex = -1;
153 this.treeView.Location = new System.Drawing.Point(0, 0);
154 this.treeView.Name = "treeView";
155 this.treeView.SelectedImageIndex = -1;
156 this.treeView.Size = new System.Drawing.Size(224, 432);
157 this.treeView.TabIndex = 0;
158 this.treeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyDown);
159 this.treeView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
160 this.treeView.KeyUp += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyUp);
161 this.treeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView_AfterSelect);
162 //
163 // toolPanel
164 //
165 this.toolPanel.Controls.Add(this.findStopButton);
166 this.toolPanel.Controls.Add(this.findButton);
167 this.toolPanel.Controls.Add(this.searchTreeCheckBox);
168 this.toolPanel.Controls.Add(this.findTextBox);
169 this.toolPanel.Controls.Add(this.refreshButton);
170 this.toolPanel.Controls.Add(this.forwardButton);
171 this.toolPanel.Controls.Add(this.backButton);
172 this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top;
173 this.toolPanel.Location = new System.Drawing.Point(0, 0);
174 this.toolPanel.Name = "toolPanel";
175 this.toolPanel.Size = new System.Drawing.Size(792, 40);
176 this.toolPanel.TabIndex = 2;
177 //
178 // findStopButton
179 //
180 this.findStopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
181 this.findStopButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
182 this.findStopButton.Location = new System.Drawing.Point(704, 8);
183 this.findStopButton.Name = "findStopButton";
184 this.findStopButton.Size = new System.Drawing.Size(72, 25);
185 this.findStopButton.TabIndex = 6;
186 this.findStopButton.Text = "Stop";
187 this.findStopButton.Visible = false;
188 this.findStopButton.Click += new System.EventHandler(this.findStopButton_Click);
189 //
190 // findButton
191 //
192 this.findButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
193 this.findButton.Enabled = false;
194 this.findButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
195 this.findButton.Location = new System.Drawing.Point(624, 8);
196 this.findButton.Name = "findButton";
197 this.findButton.Size = new System.Drawing.Size(72, 25);
198 this.findButton.TabIndex = 4;
199 this.findButton.Text = "Find";
200 this.findButton.Click += new System.EventHandler(this.findButton_Click);
201 //
202 // searchTreeCheckBox
203 //
204 this.searchTreeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
205 this.searchTreeCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System;
206 this.searchTreeCheckBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
207 this.searchTreeCheckBox.Location = new System.Drawing.Point(704, 10);
208 this.searchTreeCheckBox.Name = "searchTreeCheckBox";
209 this.searchTreeCheckBox.Size = new System.Drawing.Size(80, 22);
210 this.searchTreeCheckBox.TabIndex = 5;
211 this.searchTreeCheckBox.Text = "In Subtree";
212 this.searchTreeCheckBox.CheckedChanged += new System.EventHandler(this.searchTreeCheckBox_CheckedChanged);
213 //
214 // findTextBox
215 //
216 this.findTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
217 this.findTextBox.Location = new System.Drawing.Point(344, 10);
218 this.findTextBox.Name = "findTextBox";
219 this.findTextBox.Size = new System.Drawing.Size(272, 20);
220 this.findTextBox.TabIndex = 3;
221 this.findTextBox.Text = "";
222 this.findTextBox.TextChanged += new System.EventHandler(this.findTextBox_TextChanged);
223 this.findTextBox.Enter += new System.EventHandler(this.findTextBox_Enter);
224 //
225 // refreshButton
226 //
227 this.refreshButton.Enabled = false;
228 this.refreshButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
229 this.refreshButton.Location = new System.Drawing.Point(160, 8);
230 this.refreshButton.Name = "refreshButton";
231 this.refreshButton.Size = new System.Drawing.Size(72, 25);
232 this.refreshButton.TabIndex = 2;
233 this.refreshButton.Text = "Refresh";
234 this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
235 //
236 // forwardButton
237 //
238 this.forwardButton.Enabled = false;
239 this.forwardButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
240 this.forwardButton.Location = new System.Drawing.Point(80, 8);
241 this.forwardButton.Name = "forwardButton";
242 this.forwardButton.Size = new System.Drawing.Size(72, 25);
243 this.forwardButton.TabIndex = 1;
244 this.forwardButton.Text = "Forward";
245 this.forwardButton.Click += new System.EventHandler(this.forwardButton_Click);
246 //
247 // backButton
248 //
249 this.backButton.Enabled = false;
250 this.backButton.FlatStyle = System.Windows.Forms.FlatStyle.System;
251 this.backButton.Location = new System.Drawing.Point(8, 8);
252 this.backButton.Name = "backButton";
253 this.backButton.Size = new System.Drawing.Size(72, 25);
254 this.backButton.TabIndex = 0;
255 this.backButton.Text = "Back";
256 this.backButton.Click += new System.EventHandler(this.backButton_Click);
257 //
258 // dataPanel
259 //
260 this.dataPanel.Controls.Add(this.dataGrid);
261 this.dataPanel.Controls.Add(this.splitter);
262 this.dataPanel.Controls.Add(this.treeView);
263 this.dataPanel.Dock = System.Windows.Forms.DockStyle.Fill;
264 this.dataPanel.Location = new System.Drawing.Point(0, 40);
265 this.dataPanel.Name = "dataPanel";
266 this.dataPanel.Size = new System.Drawing.Size(792, 432);
267 this.dataPanel.TabIndex = 1;
268 //
269 // splitter
270 //
271 this.splitter.Location = new System.Drawing.Point(224, 0);
272 this.splitter.Name = "splitter";
273 this.splitter.Size = new System.Drawing.Size(6, 432);
274 this.splitter.TabIndex = 2;
275 this.splitter.TabStop = false;
276 //
277 // Inventory
278 //
279 this.AcceptButton = this.findButton;
280 this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
281 this.ClientSize = new System.Drawing.Size(792, 472);
282 this.Controls.Add(this.dataPanel);
283 this.Controls.Add(this.toolPanel);
284 this.MinimumSize = new System.Drawing.Size(700, 0);
285 this.Name = "Inventory";
286 this.Text = "MSI Inventory";
287 this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyDown);
288 this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown);
289 this.Load += new System.EventHandler(this.Inventory_Load);
290 this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyUp);
291 ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).EndInit();
292 this.toolPanel.ResumeLayout(false);
293 this.dataPanel.ResumeLayout(false);
294 this.ResumeLayout(false);
295
296 }
297 #endregion
298
299
300 #region DataProviders
301
302 private IInventoryDataProvider[] DataProviders
303 {
304 get
305 {
306 if(this.dataProviders == null)
307 {
308 ArrayList providerList = new ArrayList();
309 providerList.AddRange(FindDataProviders(Assembly.GetExecutingAssembly()));
310
311 Uri codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
312 if(codebase.IsFile)
313 {
314 foreach(string module in Directory.GetFiles(Path.GetDirectoryName(codebase.LocalPath), "*Inventory.dll"))
315 {
316 try
317 {
318 providerList.AddRange(FindDataProviders(Assembly.LoadFrom(module)));
319 }
320 catch(Exception) { }
321 }
322 }
323
324 this.dataProviders = (IInventoryDataProvider[]) providerList.ToArray(typeof(IInventoryDataProvider));
325 }
326 return this.dataProviders;
327 }
328 }
329
330 private static IList FindDataProviders(Assembly assembly)
331 {
332 ArrayList providerList = new ArrayList();
333 foreach(Type type in assembly.GetTypes())
334 {
335 if(type.IsClass)
336 {
337 foreach(Type implementedInterface in type.GetInterfaces())
338 {
339 if(implementedInterface.Equals(typeof(IInventoryDataProvider)))
340 {
341 try
342 {
343 providerList.Add(assembly.CreateInstance(type.FullName));
344 }
345 catch(Exception)
346 {
347 // Data provider's constructor threw an exception for some reason.
348 // Well, now we can't get any data from that one.
349 }
350 }
351 }
352 }
353 }
354 return providerList;
355 }
356
357 #endregion
358
359 private void GoTo(string nodePath, DataGridCell cell)
360 {
361 lock(syncRoot)
362 {
363 if(this.tablesLoading == null) return; // The tree is being loaded
364 if(this.navigating) return; // This method is already on the callstack
365
366 DataView table = (DataView) this.data[nodePath];
367 if(table != null && table == this.dataGrid.DataSource)
368 {
369 // Grid is already in view
370 if(!cell.Equals(anyCell)) this.dataGrid.CurrentCell = cell;
371 return;
372 }
373 if(cell.Equals(anyCell)) cell = zeroCell;
374
375 if(this.historyBack.Count == 0 || nodePath != (string) this.historyBack.Peek())
376 {
377 this.historyBack.Push(nodePath);
378 if(this.cellHistoryBack.Count > 0 && this.historyForward != null)
379 {
380 this.cellHistoryBack.Pop();
381 this.cellHistoryBack.Push(this.dataGrid.CurrentCell);
382 }
383 this.cellHistoryBack.Push(cell);
384 }
385 if(this.historyForward != null)
386 {
387 this.historyForward.Clear();
388 this.cellHistoryForward.Clear();
389 }
390
391 if(table != null || nodePath.Length == 0 || this.dataProviderMap[nodePath] == null)
392 {
393 this.dataGrid.CaptionText = nodePath;
394 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
395 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
396 this.dataGrid.DataSource = table;
397 this.dataGrid.CurrentCell = cell;
398 this.dataGrid.Focus();
399 }
400 else
401 {
402 this.dataGrid.CaptionText = nodePath + " (loading...)";
403 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
404 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
405 this.dataGrid.DataSource = table;
406 if(!this.tablesLoading.Contains(nodePath))
407 {
408 this.tablesLoading.Add(nodePath);
409 this.SetCursor();
410 #if SINGLETHREAD
411 this.LoadTable(nodePath);
412 #else
413 new WaitCallback(this.LoadTable).BeginInvoke(nodePath, null, null);
414 #endif
415 }
416 }
417
418 this.findButton.Enabled = this.findTextBox.Text.Length > 0 && !searching;
419
420 TreeNode treeNode = this.FindNode(nodePath);
421 if(treeNode != this.treeView.SelectedNode)
422 {
423 this.navigating = true;
424 this.treeView.SelectedNode = treeNode;
425 this.navigating = false;
426 }
427 }
428 }
429
430 private void LoadTable(object nodePathObj)
431 {
432 string nodePath = (string) nodePathObj;
433 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
434 DataView table = null;
435 if(dataProvider != null)
436 {
437 try
438 {
439 table = dataProvider.GetData(nodePath);
440 }
441 catch(Exception)
442 {
443 // Data provider threw an exception for some reason.
444 // Treat it like it returned no data.
445 }
446 }
447
448 lock(syncRoot)
449 {
450 if(this.tablesLoading == null || !tablesLoading.Contains(nodePath)) return;
451 if(table == null)
452 {
453 this.dataProviderMap.Remove(nodePath);
454 }
455 else
456 {
457 this.data[nodePath] = table;
458 }
459 this.tablesLoading.Remove(nodePath);
460 }
461 #if SINGLETHREAD
462 this.TableLoaded(nodePath);
463 #else
464 this.Invoke(new WaitCallback(this.TableLoaded), new object[] { nodePath });
465 #endif
466 }
467
468 private void TableLoaded(object nodePathObj)
469 {
470 string nodePath = (string) nodePathObj;
471 lock(syncRoot)
472 {
473 this.LoadTableStyle(nodePath);
474 if(nodePath == this.CurrentNodePath)
475 {
476 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
477 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
478 this.dataGrid.CaptionText = nodePath;
479 this.dataGrid.DataSource = this.CurrentTable;
480 this.dataGrid.CurrentCell = (DataGridCell) this.cellHistoryBack.Peek();
481 this.dataGrid.Focus();
482 }
483 this.SetCursor();
484 }
485 }
486
487 private void RefreshData()
488 {
489 lock(syncRoot)
490 {
491 this.GoTo("", zeroCell);
492 this.treeView.Nodes.Clear();
493 this.dataGrid.TableStyles.Clear();
494 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
495 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
496 this.SetControlsEnabled(false);
497 this.treeView.BeginUpdate();
498 #if SINGLETHREAD
499 this.LoadTree();
500 #else
501 new ThreadStart(this.LoadTree).BeginInvoke(null, null);
502 #endif
503 }
504 }
505
506 private void SetControlsEnabled(bool enabled)
507 {
508 this.backButton.Enabled = enabled && this.historyBack.Count > 1;
509 this.forwardButton.Enabled = enabled && this.historyForward.Count > 0;
510 this.refreshButton.Enabled = enabled;
511 this.findButton.Enabled = enabled && this.findTextBox.Text.Length > 0 && !searching;
512 }
513
514 private WaitCallback treeStatusCallback;
515 private int treeNodesLoaded;
516 private int treeNodesLoadedBase;
517 private string treeNodesLoading;
518 private void TreeLoadDataProviderStatus(int status, string currentNode)
519 {
520 if (currentNode != null)
521 {
522 this.treeNodesLoading = currentNode;
523 }
524
525 this.treeNodesLoaded = treeNodesLoadedBase + status;
526 string statusString = String.Format("Loading tree... " + this.treeNodesLoaded);
527 if (!String.IsNullOrEmpty(this.treeNodesLoading))
528 {
529 statusString += ": " + treeNodesLoading;
530 }
531
532 #if SINGLETHREAD
533 treeStatusCallback(statusString);
534 #else
535 this.Invoke(treeStatusCallback, new object[] { statusString });
536 #endif
537 }
538
539 private void UpdateTreeLoadStatus(object status)
540 {
541 if(status == null)
542 {
543 // Loading is complete.
544 this.treeView.EndUpdate();
545 this.SetCursor();
546 this.GoTo("Products", new DataGridCell(0, 0));
547 this.SetControlsEnabled(true);
548 }
549 else
550 {
551 this.dataGrid.CaptionText = (string) status;
552 }
553 }
554
555 private void LoadTree()
556 {
557 lock(syncRoot)
558 {
559 if(this.tablesLoading == null) return;
560 this.tablesLoading = null;
561 this.dataProviderMap.Clear();
562 this.data.Clear();
563 this.Invoke(new ThreadStart(this.SetCursor));
564 }
565
566 this.treeStatusCallback = new WaitCallback(UpdateTreeLoadStatus);
567 this.LoadTreeNodes();
568 this.RenderTreeNodes();
569
570 lock(syncRoot)
571 {
572 this.tablesLoading = new ArrayList();
573 }
574 // Use a status of null to signal loading complete.
575 #if SINGLETHREAD
576 this.UpdateTreeLoadStatus(null);
577 #else
578 this.Invoke(new WaitCallback(this.UpdateTreeLoadStatus), new object[] { null });
579 #endif
580 }
581
582 private void LoadTreeNodes()
583 {
584 #if SINGLETHREAD
585 this.treeStatusCallback("Loading tree... ");
586 #else
587 this.Invoke(this.treeStatusCallback, new object[] { "Loading tree... " });
588 #endif
589 this.treeNodesLoaded = 0;
590 this.treeNodesLoading = null;
591 foreach(IInventoryDataProvider dataProvider in this.DataProviders)
592 {
593 this.treeNodesLoadedBase = this.treeNodesLoaded;
594 string[] nodePaths = null;
595 try
596 {
597 nodePaths = dataProvider.GetNodes(new InventoryDataLoadStatusCallback(this.TreeLoadDataProviderStatus));
598 }
599 catch(Exception)
600 {
601 // Data provider threw an exception for some reason.
602 // Treat it like it returned no data.
603 }
604 if(nodePaths != null)
605 {
606 foreach(string nodePath in nodePaths)
607 {
608 if(!this.dataProviderMap.Contains(nodePath))
609 {
610 this.dataProviderMap.Add(nodePath, dataProvider);
611 }
612 }
613 }
614 }
615 }
616
617 private void RenderTreeNodes()
618 {
619 #if SINGLETHREAD
620 this.treeStatusCallback("Rendering tree... ");
621 #else
622 this.Invoke(this.treeStatusCallback, new object[] { "Rendering tree... " });
623 #endif
624 this.treeNodesLoaded = 0;
625 foreach(DictionaryEntry nodePathAndProvider in this.dataProviderMap)
626 {
627 string nodePath = (string) nodePathAndProvider.Key;
628 #if SINGLETHREAD
629 this.AddNode(nodePath);
630 #else
631 this.Invoke(new WaitCallback(this.AddNode), new object[] { nodePath });
632 #endif
633 }
634 }
635
636 private void LoadTableStyle(string nodePath)
637 {
638 DataView table = (DataView) this.data[nodePath];
639 if(table != null)
640 {
641 DataGridTableStyle tableStyle = this.dataGrid.TableStyles[table.Table.TableName];
642 if(tableStyle == null)
643 {
644 tableStyle = new DataGridTableStyle();
645 tableStyle.MappingName = table.Table.TableName;
646 tableStyle.RowHeadersVisible = true;
647 this.dataGrid.TableStyles.Add(tableStyle);
648 }
649 foreach(DataColumn column in table.Table.Columns)
650 {
651 if(!tableStyle.GridColumnStyles.Contains(column.ColumnName))
652 {
653 string colStyle = (string) ColumnResources.GetObject(column.ColumnName, CultureInfo.InvariantCulture);
654 if(colStyle != null)
655 {
656 string[] colStyleParts = colStyle.Split(',');
657 DataGridColumnStyle columnStyle = (colStyleParts.Length > 2 && colStyleParts[2] == "bool"
658 ? (DataGridColumnStyle) new DataGridBoolColumn() : (DataGridColumnStyle) new DataGridTextBoxColumn());
659 try { if(colStyleParts.Length > 1) columnStyle.Width = Int32.Parse(colStyleParts[1]); }
660 catch(FormatException) { }
661 columnStyle.HeaderText = colStyleParts[0];
662 columnStyle.MappingName = column.ColumnName;
663 tableStyle.GridColumnStyles.Add(columnStyle);
664 }
665 }
666 }
667 }
668 }
669
670 private static ResourceManager ColumnResources
671 {
672 get
673 {
674 if(columnResources == null)
675 {
676 columnResources = new ResourceManager(typeof(Inventory).Name + ".Columns", typeof(Inventory).Assembly);
677 }
678 return columnResources;
679 }
680 }
681 private static ResourceManager columnResources;
682
683 private void AddNode(object nodePathObj)
684 {
685 string nodePath = (string) nodePathObj;
686 string[] path = nodePath.Split('\\');
687 TreeNodeCollection nodes = this.treeView.Nodes;
688 TreeNode node = null;
689 foreach(string pathPart in path)
690 {
691 node = null;
692 for(int i = 0; i < nodes.Count; i++)
693 {
694 int c = string.CompareOrdinal(nodes[i].Text, pathPart);
695 if(c == 0)
696 {
697 node = nodes[i];
698 break;
699 }
700 else if(c > 0)
701 {
702 node = new TreeNode(pathPart);
703 nodes.Insert(i, node);
704 break;
705 }
706 }
707 if(node == null)
708 {
709 node = new TreeNode(pathPart);
710 nodes.Add(node);
711 }
712 nodes = node.Nodes;
713 }
714 if(++this.treeNodesLoaded % 1000 == 0)
715 {
716 this.UpdateTreeLoadStatus("Rendering tree... " +
717 (100 * this.treeNodesLoaded / this.dataProviderMap.Count) + "%");
718 }
719 }
720
721 public string CurrentNodePath
722 {
723 get
724 {
725 TreeNode currentNode = this.treeView.SelectedNode;
726 return currentNode != null ? currentNode.FullPath : null;
727 }
728 }
729
730 public DataView CurrentTable
731 {
732 get
733 {
734 string currentNodePath = this.CurrentNodePath;
735 return currentNodePath != null ? (DataView) this.data[this.CurrentNodePath] : null;
736 }
737 }
738
739 private TreeNode FindNode(string nodePath)
740 {
741 if(nodePath == null) return null;
742 string[] path = nodePath.Split('\\');
743 TreeNodeCollection nodes = this.treeView.Nodes;
744 TreeNode node = null;
745 foreach(string pathPart in path)
746 {
747 node = null;
748 for(int i = 0; i < nodes.Count; i++)
749 {
750 if(nodes[i].Text == pathPart)
751 {
752 node = nodes[i];
753 break;
754 }
755 }
756 if(node != null)
757 {
758 nodes = node.Nodes;
759 }
760 }
761 return node;
762 }
763
764 private void dataGrid_MouseDown(object sender, MouseEventArgs e)
765 {
766 Keys modKeys = Control.ModifierKeys;
767 if(e.Button == MouseButtons.Left && (modKeys & (Keys.Shift | Keys.Control)) == 0)
768 {
769 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
770 string link = this.GetLinkForGridHit(hit);
771 if(link != null)
772 {
773 TreeNode node = this.FindNode(link);
774 if(node != null)
775 {
776 this.treeView.SelectedNode = node;
777 node.Expand();
778 }
779 }
780 }
781 this.Inventory_MouseDown(sender, e);
782 }
783
784 private void dataGrid_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
785 {
786 //this.gridLinkTip.SetToolTip(this.dataGrid, null);
787 DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y);
788 if(hit.Type == DataGrid.HitTestType.RowHeader)
789 {
790 string link = this.GetLinkForGridHit(hit);
791 if(link != null)
792 {
793 this.mouseOverGridLink = true;
794 this.SetCursor();
795 return;
796 }
797 }
798 else if(this.mouseOverGridLink)
799 {
800 this.mouseOverGridLink = false;
801 this.SetCursor();
802 }
803 }
804
805 private void dataGrid_MouseLeave(object sender, System.EventArgs e)
806 {
807 this.mouseOverGridLink = false;
808 this.SetCursor();
809 }
810
811 private string GetLinkForGridHit(DataGrid.HitTestInfo hit)
812 {
813 if(hit.Type == DataGrid.HitTestType.RowHeader && this.tablesLoading != null)
814 {
815 string nodePath = this.CurrentNodePath;
816 DataView table = (DataView) this.data[nodePath];
817 if(table != null)
818 {
819 DataRow row = table[hit.Row].Row;
820 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
821 return dataProvider.GetLink(nodePath, table[hit.Row].Row);
822 }
823 }
824 return null;
825 }
826
827 private void HistoryBack()
828 {
829 lock(syncRoot)
830 {
831 if(this.historyBack.Count > 1)
832 {
833 string nodePath = (string) this.historyBack.Pop();
834 this.cellHistoryBack.Pop();
835 DataGridCell cell = this.dataGrid.CurrentCell;
836 Stack saveForward = this.historyForward;
837 this.historyForward = null;
838 this.GoTo((string) this.historyBack.Pop(), (DataGridCell) this.cellHistoryBack.Pop());
839 this.historyForward = saveForward;
840 this.historyForward.Push(nodePath);
841 this.cellHistoryForward.Push(cell);
842 this.backButton.Enabled = this.historyBack.Count > 1;
843 this.forwardButton.Enabled = this.historyForward.Count > 0;
844 }
845 }
846 }
847
848 private void HistoryForward()
849 {
850 lock(syncRoot)
851 {
852 if(this.historyForward.Count > 0)
853 {
854 string nodePath = (string) this.historyForward.Pop();
855 DataGridCell cell = (DataGridCell) this.cellHistoryForward.Pop();
856 Stack saveForward = this.historyForward;
857 this.historyForward = null;
858 this.GoTo(nodePath, cell);
859 this.historyForward = saveForward;
860 this.backButton.Enabled = this.historyBack.Count > 1;
861 this.forwardButton.Enabled = this.historyForward.Count > 0;
862 }
863 }
864 }
865
866 #region Find
867
868 private void Find()
869 {
870 this.BeginFind();
871 object[] findNextArgs = new object[] { this.CurrentNodePath, this.dataGrid.CurrentCell, this.treeView.SelectedNode };
872 #if SINGLETHREAD
873 this.FindNext(findNextArgs);
874 #else
875 new WaitCallback(this.FindNext).BeginInvoke(findNextArgs, null, null);
876 #endif
877 }
878
879 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
880 private void FindNext(object start)
881 {
882 string nodePath = (string) ((object[]) start)[0];
883 DataGridCell startCell = (DataGridCell) ((object[]) start)[1];
884 TreeNode searchNode = (TreeNode) ((object[]) start)[2];
885 DataGridCell endCell = startCell;
886
887 string searchString = this.findTextBox.Text;
888 if(searchString.Length == 0) return;
889
890 bool ignoreCase = true; // TODO: make this a configurable option?
891 if(ignoreCase) searchString = searchString.ToLowerInvariant();
892
893 if(!this.searchTreeCheckBox.Checked)
894 {
895 DataGridCell foundCell;
896 startCell.ColumnNumber++;
897 if(FindInTable((DataView) this.data[nodePath], searchString, ignoreCase,
898 startCell, startCell, true, out foundCell))
899 {
900 #if SINGLETHREAD
901 this.EndFind(new object[] { nodePath, foundCell });
902 #else
903 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodePath, foundCell } });
904 #endif
905 return;
906 }
907 }
908 else
909 {
910 if(this.continueSearchRoot != null)
911 {
912 searchNode = this.FindNode(this.continueSearchRoot);
913 startCell = this.continueSearchCell;
914 endCell = this.continueSearchEndCell;
915 }
916 else
917 {
918 this.continueSearchRoot = searchNode.FullPath;
919 this.continueSearchPath = this.continueSearchRoot;
920 this.continueSearchEndCell = endCell;
921 }
922 //if(searchNode == null) return;
923 ArrayList nodesList = new ArrayList();
924 nodesList.Add(searchNode);
925 this.GetFlatTreeNodes(searchNode.Nodes, nodesList, true, this.continueSearchRoot);
926 TreeNode[] nodes = (TreeNode[]) nodesList.ToArray(typeof(TreeNode));
927 int startNode = nodesList.IndexOf(this.FindNode(this.continueSearchPath));
928 DataGridCell foundCell;
929 startCell.ColumnNumber++;
930 for(int i = startNode; i < nodes.Length; i++)
931 {
932 if(this.stopSearch) break;
933 DataGridCell startCellOnThisNode = zeroCell;
934 if(i == startNode) startCellOnThisNode = startCell;
935 DataView table = this.GetTableForSearch(nodes[i].FullPath);
936 if(table != null)
937 {
938 if(FindInTable(table, searchString, ignoreCase, startCellOnThisNode, zeroCell, false, out foundCell))
939 {
940 #if SINGLETHREAD
941 this.EndFind(new object[] { nodes[i].FullPath, foundCell });
942 #else
943 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodes[i].FullPath, foundCell } });
944 #endif
945 return;
946 }
947 }
948 }
949 if(!this.stopSearch)
950 {
951 DataView table = this.GetTableForSearch(searchNode.FullPath);
952 if(table != null)
953 {
954 if(FindInTable(table, searchString, ignoreCase, zeroCell, endCell, false, out foundCell))
955 {
956 #if SINGLETHREAD
957 this.EndFind(new object[] { searchNode.FullPath, foundCell });
958 #else
959 this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { searchNode.FullPath, foundCell } });
960 #endif
961 return;
962 }
963 }
964 }
965 }
966 #if SINGLETHREAD
967 this.EndFind(null);
968 #else
969 this.Invoke(new WaitCallback(this.EndFind), new object[] { null });
970 #endif
971 }
972
973 private DataView GetTableForSearch(string nodePath)
974 {
975 DataView table = (DataView) this.data[nodePath];
976 string status = nodePath;
977 if(table == null) status = status + " (loading)";
978 #if SINGLETHREAD
979 this.FindStatus(nodePath);
980 #else
981 this.Invoke(new WaitCallback(this.FindStatus), new object[] { status });
982 #endif
983 if(table == null)
984 {
985 this.tablesLoading.Add(nodePath);
986 this.Invoke(new ThreadStart(this.SetCursor));
987 this.LoadTable(nodePath);
988 table = (DataView) this.data[nodePath];
989 }
990 return table;
991 }
992
993 private void GetFlatTreeNodes(TreeNodeCollection nodes, IList resultsList, bool searchable, string searchRoot)
994 {
995 foreach(TreeNode node in nodes)
996 {
997 string nodePath = node.FullPath;
998 IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath];
999 if(!searchable || (dataProvider != null && dataProvider.IsNodeSearchable(searchRoot, nodePath)))
1000 {
1001 resultsList.Add(node);
1002 }
1003 GetFlatTreeNodes(node.Nodes, resultsList, searchable, searchRoot);
1004 }
1005 }
1006
1007 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
1008 private bool FindInTable(DataView table, string searchString, bool lowerCase,
1009 DataGridCell startCell, DataGridCell endCell, bool wrap, out DataGridCell foundCell)
1010 {
1011 foundCell = new DataGridCell(-1, -1);
1012 if(table == null) return false;
1013 if(startCell.RowNumber < 0) startCell.RowNumber = 0;
1014 if(startCell.ColumnNumber < 0) startCell.ColumnNumber = 0;
1015 for(int searchRow = startCell.RowNumber; searchRow < table.Count; searchRow++)
1016 {
1017 if(this.stopSearch) break;
1018 if(endCell.RowNumber > startCell.RowNumber && searchRow > endCell.RowNumber) break;
1019
1020 DataRowView tableRow = table[searchRow];
1021 for(int searchCol = (searchRow == startCell.RowNumber
1022 ? startCell.ColumnNumber : 0); searchCol < table.Table.Columns.Count; searchCol++)
1023 {
1024 if(this.stopSearch) break;
1025 if(endCell.RowNumber > startCell.RowNumber && searchRow == endCell.RowNumber
1026 && searchCol >= endCell.ColumnNumber) break;
1027
1028 string value = tableRow[searchCol].ToString();
1029 if(lowerCase) value = value.ToLowerInvariant();
1030 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1031 {
1032 foundCell.RowNumber = searchRow;
1033 foundCell.ColumnNumber = searchCol;
1034 return true;
1035 }
1036 }
1037 }
1038 if(wrap)
1039 {
1040 for(int searchRow = 0; searchRow <= endCell.RowNumber; searchRow++)
1041 {
1042 if(this.stopSearch) break;
1043 DataRowView tableRow = table[searchRow];
1044 for(int searchCol = 0; searchCol < (searchRow == endCell.RowNumber
1045 ? endCell.ColumnNumber : table.Table.Columns.Count); searchCol++)
1046 {
1047 if(this.stopSearch) break;
1048 string value = tableRow[searchCol].ToString();
1049 if(lowerCase) value = value.ToLowerInvariant();
1050 if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0)
1051 {
1052 foundCell.RowNumber = searchRow;
1053 foundCell.ColumnNumber = searchCol;
1054 return true;
1055 }
1056 }
1057 }
1058 }
1059 return false;
1060 }
1061
1062 private void BeginFind()
1063 {
1064 lock(syncRoot)
1065 {
1066 this.findButton.Enabled = false;
1067 this.findButton.Text = "Searching...";
1068 this.findTextBox.Enabled = false;
1069 this.searchTreeCheckBox.Visible = false;
1070 this.findStopButton.Visible = true;
1071 this.refreshButton.Enabled = false;
1072 this.searching = true;
1073 this.stopSearch = false;
1074 this.SetCursor();
1075 }
1076 }
1077
1078 private void FindStatus(object status)
1079 {
1080 lock(syncRoot)
1081 {
1082 this.dataGrid.CaptionText = "Searching... " + (string) status;
1083 this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption;
1084 this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText;
1085 }
1086 }
1087
1088 private void EndFind(object result)
1089 {
1090 lock(syncRoot)
1091 {
1092 this.searching = false;
1093 this.refreshButton.Enabled = true;
1094 this.findStopButton.Visible = false;
1095 this.searchTreeCheckBox.Visible = true;
1096 this.findTextBox.Enabled = true;
1097 this.findButton.Text = "Find";
1098 this.findButton.Enabled = true;
1099 this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption;
1100 this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText;
1101 this.dataGrid.CaptionText = this.CurrentNodePath;
1102 if(result != null)
1103 {
1104 string nodePath = (string) ((object[]) result)[0];
1105 DataGridCell foundCell = (DataGridCell) ((object[]) result)[1];
1106 this.GoTo(nodePath, foundCell);
1107 this.dataGrid.Focus();
1108 this.continueSearchPath = nodePath;
1109 this.continueSearchCell = foundCell;
1110 if(this.searchTreeCheckBox.Checked) this.searchTreeCheckBox.Text = "Continue";
1111 }
1112 else
1113 {
1114 this.continueSearchRoot = null;
1115 this.continueSearchPath = null;
1116 this.searchTreeCheckBox.Text = "In Subtree";
1117 }
1118 this.SetCursor();
1119 }
1120 }
1121
1122 private void SetCursor()
1123 {
1124 if(this.mouseOverGridLink)
1125 {
1126 Keys modKeys = Control.ModifierKeys;
1127 if((modKeys & (Keys.Shift | Keys.Control)) == 0)
1128 {
1129 this.Cursor = Cursors.Hand;
1130 return;
1131 }
1132 }
1133 if(this.tablesLoading == null || this.tablesLoading.Count > 0 || this.searching)
1134 {
1135 this.Cursor = Cursors.AppStarting;
1136 return;
1137 }
1138 this.Cursor = Cursors.Arrow;
1139 }
1140
1141 #endregion
1142
1143 #region EventHandlers
1144
1145 private void Inventory_Load(object sender, System.EventArgs e)
1146 {
1147 this.RefreshData();
1148 }
1149 private void refreshButton_Click(object sender, System.EventArgs e)
1150 {
1151 this.RefreshData();
1152 }
1153 private void Inventory_MouseDown(object sender, MouseEventArgs e)
1154 {
1155 if(e.Button == MouseButtons.XButton1) this.HistoryBack();
1156 else if(e.Button == MouseButtons.XButton2) this.HistoryForward();
1157 }
1158 private void Inventory_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1159 {
1160 this.SetCursor();
1161 if(e.KeyCode == Keys.F3) this.Find();
1162 else if(e.KeyCode == Keys.F && (e.Modifiers | Keys.Control) != 0) this.findTextBox.Focus();
1163 else if(e.KeyCode == Keys.BrowserBack) this.HistoryBack();
1164 else if(e.KeyCode == Keys.BrowserForward) this.HistoryForward();
1165 else return;
1166 e.Handled = true;
1167 }
1168 private void treeView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1169 {
1170 this.Inventory_KeyDown(sender, e);
1171 }
1172 private void dataGrid_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
1173 {
1174 this.Inventory_KeyDown(sender, e);
1175 }
1176 private void Inventory_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1177 {
1178 this.SetCursor();
1179 }
1180 private void treeView_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1181 {
1182 this.Inventory_KeyDown(sender, e);
1183 }
1184 private void dataGrid_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
1185 {
1186 this.Inventory_KeyDown(sender, e);
1187 }
1188 private void treeView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
1189 {
1190 this.GoTo(e.Node.FullPath, anyCell);
1191 }
1192 private void backButton_Click(object sender, System.EventArgs e)
1193 {
1194 this.HistoryBack();
1195 }
1196 private void forwardButton_Click(object sender, System.EventArgs e)
1197 {
1198 this.HistoryForward();
1199 }
1200 private void findTextBox_TextChanged(object sender, System.EventArgs e)
1201 {
1202 this.findButton.Enabled = this.findTextBox.Text.Length > 0 &&
1203 this.tablesLoading != null && this.treeView.SelectedNode != null && !searching;
1204 this.searchTreeCheckBox.Text = "In Subtree";
1205 this.continueSearchRoot = null;
1206 }
1207 private void findButton_Click(object sender, System.EventArgs e)
1208 {
1209 this.Find();
1210 }
1211 private void findTextBox_Enter(object sender, System.EventArgs e)
1212 {
1213 findTextBox.SelectAll();
1214 }
1215 private void findStopButton_Click(object sender, System.EventArgs e)
1216 {
1217 this.stopSearch = true;
1218 }
1219
1220 private void searchTreeCheckBox_CheckedChanged(object sender, System.EventArgs e)
1221 {
1222 if(!searchTreeCheckBox.Checked && searchTreeCheckBox.Text == "Continue")
1223 {
1224 this.searchTreeCheckBox.Text = "In Subtree";
1225 }
1226 }
1227
1228 #endregion
1229
1230 }
1231}
diff --git a/src/samples/Dtf/Inventory/Inventory.csproj b/src/samples/Dtf/Inventory/Inventory.csproj
new file mode 100644
index 00000000..6dc1cfd3
--- /dev/null
+++ b/src/samples/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.Samples.Inventory</RootNamespace>
10 <AssemblyName>Inventory</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 <ApplicationIcon>Inventory.ico</ApplicationIcon>
13 <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Compile Include="components.cs" />
18 <Compile Include="Features.cs" />
19 <Compile Include="IInventoryDataProvider.cs" />
20 <Compile Include="Inventory.cs">
21 <SubType>Form</SubType>
22 </Compile>
23 <Compile Include="msiutils.cs" />
24 <Compile Include="patches.cs" />
25 <Compile Include="products.cs" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <Content Include="Inventory.ico" />
30 </ItemGroup>
31
32 <ItemGroup>
33 <Reference Include="System" />
34 <Reference Include="System.Data" />
35 <Reference Include="System.Drawing" />
36 <Reference Include="System.Windows.Forms" />
37 <Reference Include="System.Xml" />
38 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
39 </ItemGroup>
40
41 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
42</Project>
diff --git a/src/samples/Dtf/Inventory/Inventory.ico b/src/samples/Dtf/Inventory/Inventory.ico
new file mode 100644
index 00000000..d5757f7a
--- /dev/null
+++ b/src/samples/Dtf/Inventory/Inventory.ico
Binary files differ
diff --git a/src/samples/Dtf/Inventory/Inventory.resx b/src/samples/Dtf/Inventory/Inventory.resx
new file mode 100644
index 00000000..9aeb4d2c
--- /dev/null
+++ b/src/samples/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/samples/Dtf/Inventory/components.cs b/src/samples/Dtf/Inventory/components.cs
new file mode 100644
index 00000000..c5147084
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
16{
17 /// <summary>
18 /// Provides inventory data about components of products installed on the system.
19 /// </summary>
20 public class ComponentsInventory : IInventoryDataProvider
21 {
22 private static object syncRoot = new object();
23
24 public ComponentsInventory()
25 {
26 }
27
28 public string Description
29 {
30 get { return "Components of installed products"; }
31 }
32
33 private Hashtable componentProductsMap;
34
35 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
36 {
37 ArrayList nodes = new ArrayList();
38 componentProductsMap = new Hashtable();
39 foreach(ProductInstallation product in ProductInstallation.AllProducts)
40 {
41 string productName = MsiUtils.GetProductName(product.ProductCode);
42 statusCallback(nodes.Count, String.Format(@"Products\{0}", productName));
43
44 try
45 {
46 IntPtr hWnd = IntPtr.Zero;
47 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
48 lock(syncRoot) // Only one Installer session can be active at a time
49 {
50 using (Session session = Installer.OpenProduct(product.ProductCode))
51 {
52 statusCallback(nodes.Count, String.Format(@"Products\{0}\Features", productName));
53 IList<string> features = session.Database.ExecuteStringQuery("SELECT `Feature` FROM `Feature`");
54 string[] featuresArray = new string[features.Count];
55 features.CopyTo(featuresArray, 0);
56 Array.Sort(featuresArray, 0, featuresArray.Length, StringComparer.OrdinalIgnoreCase);
57 foreach (string feature in featuresArray)
58 {
59 nodes.Add(String.Format(@"Products\{0}\Features\{1}", productName, feature));
60 }
61 statusCallback(nodes.Count, String.Format(@"Products\{0}\Components", productName));
62 nodes.Add(String.Format(@"Products\{0}\Components", productName));
63 IList<string> components = session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`");
64 for (int i = 0; i < components.Count; i++)
65 {
66 string component = components[i];
67 if (component.Length > 0)
68 {
69 nodes.Add(String.Format(@"Products\{0}\Components\{1}", productName, component));
70 ArrayList sharingProducts = (ArrayList) componentProductsMap[component];
71 if (sharingProducts == null)
72 {
73 sharingProducts = new ArrayList();
74 componentProductsMap[component] = sharingProducts;
75 }
76 sharingProducts.Add(product.ProductCode);
77 }
78 if (i % 100 == 0) statusCallback(nodes.Count, null);
79 }
80 nodes.Add(String.Format(@"Products\{0}\Files", productName));
81 nodes.Add(String.Format(@"Products\{0}\Registry", productName));
82 statusCallback(nodes.Count, String.Empty);
83 }
84 }
85 }
86 catch(InstallerException) { }
87 }
88 statusCallback(nodes.Count, @"Products\...\Components\...\Sharing");
89 foreach (DictionaryEntry componentProducts in componentProductsMap)
90 {
91 string component = (string) componentProducts.Key;
92 ArrayList products = (ArrayList) componentProducts.Value;
93 if(products.Count > 1)
94 {
95 foreach(string productCode in products)
96 {
97 nodes.Add(String.Format(@"Products\{0}\Components\{1}\Sharing", MsiUtils.GetProductName(productCode), component));
98 }
99 }
100 }
101 statusCallback(nodes.Count, String.Empty);
102 return (string[]) nodes.ToArray(typeof(string));
103 }
104
105 public bool IsNodeSearchable(string searchRoot, string searchNode)
106 {
107 string[] rootPath = searchRoot.Split('\\');
108 string[] nodePath = searchNode.Split('\\');
109 if(rootPath.Length < 3 && nodePath.Length >= 3 && nodePath[0] == "Products" && nodePath[2] == "Components")
110 {
111 // When searching an entire product, don't search the "Components" subtree --
112 // it just has duplicate data from the Files and Registry table. And if you
113 // really want to know about the component, it's only a click away from
114 // those other tables.
115 return false;
116 }
117 return true;
118 }
119
120 public DataView GetData(string nodePath)
121 {
122 string[] path = nodePath.Split('\\');
123
124 if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
125 {
126 return GetFeatureComponentsData(MsiUtils.GetProductCode(path[1]), path[3]);
127 }
128 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
129 {
130 return GetProductComponentsData(MsiUtils.GetProductCode(path[1]));
131 }
132 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Components")
133 {
134 return GetComponentData(MsiUtils.GetProductCode(path[1]), path[3]);
135 }
136 else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
137 {
138 return GetComponentProductsData(path[3]);
139 }
140 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
141 {
142 return GetProductFilesData(MsiUtils.GetProductCode(path[1]));
143 }
144 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
145 {
146 return GetProductRegistryData(MsiUtils.GetProductCode(path[1]));
147 }
148 return null;
149 }
150
151 public DataView GetComponentData(string productCode, string componentCode)
152 {
153 DataTable table = new DataTable("ProductComponentItems");
154 table.Locale = CultureInfo.InvariantCulture;
155 table.Columns.Add("ProductComponentItemsIsKey", typeof(bool));
156 table.Columns.Add("ProductComponentItemsKey", typeof(string));
157 table.Columns.Add("ProductComponentItemsPath", typeof(string));
158 table.Columns.Add("ProductComponentItemsExists", typeof(bool));
159 table.Columns.Add("ProductComponentItemsDbVersion", typeof(string));
160 table.Columns.Add("ProductComponentItemsInstalledVersion", typeof(string));
161 table.Columns.Add("ProductComponentItemsInstalledMatch", typeof(bool));
162 try
163 {
164 IntPtr hWnd = IntPtr.Zero;
165 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
166 lock(syncRoot) // Only one Installer session can be active at a time
167 {
168 using(Session session = Installer.OpenProduct(productCode))
169 {
170 session.DoAction("CostInitialize");
171 session.DoAction("FileCost");
172 session.DoAction("CostFinalize");
173
174 foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, false))
175 {
176 table.Rows.Add(row);
177 }
178 foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, false))
179 {
180 table.Rows.Add(row);
181 }
182 }
183 }
184 return new DataView(table, "", "ProductComponentItemsPath ASC", DataViewRowState.CurrentRows);
185 }
186 catch(InstallerException) { }
187 return null;
188 }
189
190 private object[][] GetComponentFilesRows(string productCode, string componentCode, Session session, bool includeComponent)
191 {
192 ArrayList rows = new ArrayList();
193 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
194
195 string componentKey = (string) session.Database.ExecuteScalar(
196 "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
197 if(componentKey == null) return null;
198 int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
199 "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
200 bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
201 if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
202 string keyPath = (string) session.Database.ExecuteScalar(
203 "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
204
205 using (View view = session.Database.OpenView("SELECT `File`, `FileName`, `Version`, `Language`, " +
206 "`Attributes` FROM `File` WHERE `Component_` = '{0}'", componentKey))
207 {
208 view.Execute();
209
210 foreach (Record rec in view) using (rec)
211 {
212 string fileKey = (string) rec["File"];
213 bool isKey = !registryKeyPath && keyPath == fileKey;
214
215 string dbVersion = (string) rec["Version"];
216 bool versionedFile = dbVersion.Length != 0;
217 if(versionedFile)
218 {
219 string language = (string) rec["Language"];
220 if(language.Length > 0)
221 {
222 dbVersion = dbVersion + " (" + language + ")";
223 }
224 }
225 else if(session.Database.Tables.Contains("MsiFileHash"))
226 {
227 IList<int> hash = session.Database.ExecuteIntegerQuery("SELECT `HashPart1`, `HashPart2`, " +
228 "`HashPart3`, `HashPart4` FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey);
229 if(hash != null && hash.Count == 4)
230 {
231 dbVersion = this.GetFileHashString(hash);
232 }
233 }
234
235 string filePath = GetLongFileName((string) rec["FileName"]);
236 bool exists = false;
237 bool installedMatch = false;
238 string installedVersion = "";
239 if(!registryKeyPath && componentPath.Length > 0)
240 {
241 filePath = Path.Combine(componentPath, filePath);
242
243 if(File.Exists(filePath))
244 {
245 exists = true;
246 if(versionedFile)
247 {
248 installedVersion = Installer.GetFileVersion(filePath);
249 string language = Installer.GetFileLanguage(filePath);
250 if(language.Length > 0)
251 {
252 installedVersion = installedVersion + " (" + language + ")";
253 }
254 }
255 else
256 {
257 int[] hash = new int[4];
258 Installer.GetFileHash(filePath, hash);
259 installedVersion = this.GetFileHashString(hash);
260 }
261 installedMatch = installedVersion == dbVersion;
262 }
263 }
264
265 object[] row;
266 if(includeComponent) row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch, componentCode };
267 else row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch };
268 rows.Add(row);
269 }
270 }
271
272 return (object[][]) rows.ToArray(typeof(object[]));
273 }
274
275 private string GetLongFileName(string fileName)
276 {
277 string[] fileNames = fileName.Split('|');
278 return fileNames.Length == 1? fileNames[0] : fileNames[1];
279 }
280
281 private string GetFileHashString(IList<int> hash)
282 {
283 return String.Format("{0:X8}{1:X8}{2:X8}{3:X8}", (uint) hash[0], (uint) hash[1], (uint) hash[2], (uint) hash[3]);
284 }
285
286 private object[][] GetComponentRegistryRows(string productCode, string componentCode, Session session, bool includeComponent)
287 {
288 ArrayList rows = new ArrayList();
289 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
290
291 string componentKey = (string) session.Database.ExecuteScalar(
292 "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode);
293 if(componentKey == null) return null;
294 int attributes = Convert.ToInt32(session.Database.ExecuteScalar(
295 "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey));
296 bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0;
297 if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath);
298 string keyPath = (string) session.Database.ExecuteScalar(
299 "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey);
300
301 using (View view = session.Database.OpenView("SELECT `Registry`, `Root`, `Key`, `Name`, " +
302 "`Value` FROM `Registry` WHERE `Component_` = '{0}'", componentKey))
303 {
304 view.Execute();
305
306 foreach (Record rec in view) using (rec)
307 {
308 string regName = (string) rec["Name"];
309 if(regName == "-") continue; // Don't list deleted keys
310
311 string regTableKey = (string) rec["Registry"];
312 bool isKey = registryKeyPath && keyPath == regTableKey;
313 string regPath = this.GetRegistryPath(session, (RegistryRoot) Convert.ToInt32(rec["Root"]),
314 (string) rec["Key"], (string) rec["Name"]);
315
316 string dbValue;
317 using(Record formatRec = new Record(0))
318 {
319 formatRec[0] = rec["Value"];
320 dbValue = session.FormatRecord(formatRec);
321 }
322
323 string installedValue = this.GetRegistryValue(regPath);
324 bool exists = installedValue != null;
325 if(!exists) installedValue = "";
326 bool match = installedValue == dbValue;
327
328 object[] row;
329 if(includeComponent) row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match, componentCode };
330 else row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match };
331 rows.Add(row);
332 }
333 }
334
335 return (object[][]) rows.ToArray(typeof(object[]));
336 }
337
338 private string GetRegistryPath(Session session, RegistryRoot root, string key, string name)
339 {
340 bool allUsers = session.EvaluateCondition("ALLUSERS = 1", true);
341 string rootName = "????";
342 switch(root)
343 {
344 case RegistryRoot.LocalMachine : rootName = "HKLM"; break;
345 case RegistryRoot.CurrentUser : rootName = "HKCU"; break;
346 case RegistryRoot.Users : rootName = "HKU"; break;
347 case RegistryRoot.UserOrMachine: rootName = (allUsers ? "HKLM" : "HKCU"); break;
348 case RegistryRoot.ClassesRoot : rootName = (allUsers ? @"HKLM\Software\Classes" : @"HKCU\Software\Classes"); break;
349 // TODO: Technically, RegistryRoot.ClassesRoot should be under HKLM on NT4.
350 }
351 if(name.Length == 0) name = "(Default)";
352 if(name == "+" || name == "*") name = "";
353 else name = " : " + name;
354 using(Record formatRec = new Record(0))
355 {
356 formatRec[0] = String.Format(@"{0}\{1}{2}", rootName, key, name);
357 return session.FormatRecord(formatRec);
358 }
359 }
360
361 private string GetRegistryValue(string regPath)
362 {
363 string valueName = null;
364 int iColon = regPath.IndexOf(" : ", StringComparison.Ordinal) + 1;
365 if(iColon > 0)
366 {
367 valueName = regPath.Substring(iColon + 2);
368 regPath = regPath.Substring(0, iColon - 1);
369 }
370 if(valueName == "(Default)") valueName = "";
371
372 RegistryKey root;
373 if(regPath.StartsWith(@"HKLM\", StringComparison.Ordinal))
374 {
375 root = Registry.LocalMachine;
376 regPath = regPath.Substring(5);
377 }
378 else if(regPath.StartsWith(@"HKCU\", StringComparison.Ordinal))
379 {
380 root = Registry.CurrentUser;
381 regPath = regPath.Substring(5);
382 }
383 else if(regPath.StartsWith(@"HKU\", StringComparison.Ordinal))
384 {
385 root = Registry.Users;
386 regPath = regPath.Substring(4);
387 }
388 else return null;
389
390 using(RegistryKey regKey = root.OpenSubKey(regPath))
391 {
392 if(regKey != null)
393 {
394 if(valueName == null)
395 {
396 // Just checking for the existence of the key.
397 return "";
398 }
399 object value = regKey.GetValue(valueName);
400 if(value is string[])
401 {
402 value = String.Join("[~]", (string[]) value);
403 }
404 else if(value is int)
405 {
406 value = "#" + value.ToString();
407 }
408 else if(value is byte[])
409 {
410 byte[] valueBytes = (byte[]) value;
411 StringBuilder byteString = new StringBuilder("#x");
412 for(int i = 0; i < valueBytes.Length; i++)
413 {
414 byteString.Append(valueBytes[i].ToString("x2"));
415 }
416 value = byteString.ToString();
417 }
418 return (value != null ? value.ToString() : null);
419 }
420 }
421 return null;
422 }
423
424 public DataView GetProductFilesData(string productCode)
425 {
426 DataTable table = new DataTable("ProductFiles");
427 table.Locale = CultureInfo.InvariantCulture;
428 table.Columns.Add("ProductFilesIsKey", typeof(bool));
429 table.Columns.Add("ProductFilesKey", typeof(string));
430 table.Columns.Add("ProductFilesPath", typeof(string));
431 table.Columns.Add("ProductFilesExists", typeof(bool));
432 table.Columns.Add("ProductFilesDbVersion", typeof(string));
433 table.Columns.Add("ProductFilesInstalledVersion", typeof(string));
434 table.Columns.Add("ProductFilesInstalledMatch", typeof(bool));
435 table.Columns.Add("ProductFilesComponentID", typeof(string));
436 try
437 {
438 IntPtr hWnd = IntPtr.Zero;
439 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
440 lock(syncRoot) // Only one Installer session can be active at a time
441 {
442 using(Session session = Installer.OpenProduct(productCode))
443 {
444 session.DoAction("CostInitialize");
445 session.DoAction("FileCost");
446 session.DoAction("CostFinalize");
447
448 foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
449 {
450 foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, true))
451 {
452 table.Rows.Add(row);
453 }
454 }
455 }
456 }
457 return new DataView(table, "", "ProductFilesPath ASC", DataViewRowState.CurrentRows);
458 }
459 catch(InstallerException) { }
460 return null;
461 }
462
463 public DataView GetProductRegistryData(string productCode)
464 {
465 DataTable table = new DataTable("ProductRegistry");
466 table.Locale = CultureInfo.InvariantCulture;
467 table.Columns.Add("ProductRegistryIsKey", typeof(bool));
468 table.Columns.Add("ProductRegistryKey", typeof(string));
469 table.Columns.Add("ProductRegistryPath", typeof(string));
470 table.Columns.Add("ProductRegistryExists", typeof(bool));
471 table.Columns.Add("ProductRegistryDbVersion", typeof(string));
472 table.Columns.Add("ProductRegistryInstalledVersion", typeof(string));
473 table.Columns.Add("ProductRegistryInstalledMatch", typeof(bool));
474 table.Columns.Add("ProductRegistryComponentID", typeof(string));
475 try
476 {
477 IntPtr hWnd = IntPtr.Zero;
478 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
479 lock(syncRoot) // Only one Installer session can be active at a time
480 {
481 using(Session session = Installer.OpenProduct(productCode))
482 {
483 session.DoAction("CostInitialize");
484 session.DoAction("FileCost");
485 session.DoAction("CostFinalize");
486
487 foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"))
488 {
489 foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, true))
490 {
491 table.Rows.Add(row);
492 }
493 }
494 }
495 }
496 return new DataView(table, "", "ProductRegistryPath ASC", DataViewRowState.CurrentRows);
497 }
498 catch(InstallerException) { }
499 return null;
500 }
501
502 public DataView GetComponentProductsData(string componentCode)
503 {
504 DataTable table = new DataTable("ComponentProducts");
505 table.Locale = CultureInfo.InvariantCulture;
506 table.Columns.Add("ComponentProductsProductName", typeof(string));
507 table.Columns.Add("ComponentProductsProductCode", typeof(string));
508 table.Columns.Add("ComponentProductsComponentPath", typeof(string));
509
510 if(this.componentProductsMap != null)
511 {
512 ArrayList componentProducts = (ArrayList) this.componentProductsMap[componentCode];
513 foreach(string productCode in componentProducts)
514 {
515 string productName = MsiUtils.GetProductName(productCode);
516 string componentPath = new ComponentInstallation(componentCode, productCode).Path;
517 table.Rows.Add(new object[] { productName, productCode, componentPath });
518 }
519 return new DataView(table, "", "ComponentProductsProductName ASC", DataViewRowState.CurrentRows);
520 }
521 return null;
522 }
523
524 public DataView GetProductComponentsData(string productCode)
525 {
526 DataTable table = new DataTable("ProductComponents");
527 table.Locale = CultureInfo.InvariantCulture;
528 table.Columns.Add("ProductComponentsComponentName", typeof(string));
529 table.Columns.Add("ProductComponentsComponentID", typeof(string));
530 table.Columns.Add("ProductComponentsInstallState", typeof(string));
531
532 try
533 {
534 IntPtr hWnd = IntPtr.Zero;
535 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
536 lock(syncRoot) // Only one Installer session can be active at a time
537 {
538 using(Session session = Installer.OpenProduct(productCode))
539 {
540 session.DoAction("CostInitialize");
541 session.DoAction("FileCost");
542 session.DoAction("CostFinalize");
543
544 IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
545 "SELECT `Component`, `ComponentId` FROM `Component`");
546
547 for (int i = 0; i < componentsAndIds.Count; i += 2)
548 {
549 if(componentsAndIds[i+1] == "Temporary Id") continue;
550 InstallState compState = session.Components[componentsAndIds[i]].CurrentState;
551 table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1],
552 (compState == InstallState.Advertised ? "Advertised" : compState.ToString())});
553 }
554 }
555 }
556 return new DataView(table, "", "ProductComponentsComponentName ASC", DataViewRowState.CurrentRows);
557 }
558 catch(InstallerException) { }
559 return null;
560 }
561
562 public DataView GetFeatureComponentsData(string productCode, string feature)
563 {
564 DataTable table = new DataTable("ProductFeatureComponents");
565 table.Locale = CultureInfo.InvariantCulture;
566 table.Columns.Add("ProductFeatureComponentsComponentName", typeof(string));
567 table.Columns.Add("ProductFeatureComponentsComponentID", typeof(string));
568
569 try
570 {
571 IntPtr hWnd = IntPtr.Zero;
572 Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd);
573 lock(syncRoot) // Only one Installer session can be active at a time
574 {
575 using(Session session = Installer.OpenProduct(productCode))
576 {
577 IList<string> componentsAndIds = session.Database.ExecuteStringQuery(
578 "SELECT `FeatureComponents`.`Component_`, " +
579 "`Component`.`ComponentId` FROM `FeatureComponents`, `Component` " +
580 "WHERE `FeatureComponents`.`Component_` = `Component`.`Component` " +
581 "AND `FeatureComponents`.`Feature_` = '{0}'", feature);
582 for (int i = 0; i < componentsAndIds.Count; i += 2)
583 {
584 table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1] });
585 }
586 }
587 }
588 return new DataView(table, "", "ProductFeatureComponentsComponentName ASC", DataViewRowState.CurrentRows);
589 }
590 catch(InstallerException) { }
591 return null;
592 }
593
594 public string GetLink(string nodePath, DataRow row)
595 {
596 string[] path = nodePath.Split('\\');
597
598 if(path.Length == 3 && path[0] == "Products" && path[2] == "Components")
599 {
600 string component = (string) row["ProductComponentsComponentID"];
601 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
602 }
603 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Features")
604 {
605 string component = (string) row["ProductFeatureComponentsComponentID"];
606 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
607 }
608 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files")
609 {
610 string component = (string) row["ProductFilesComponentID"];
611 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
612 }
613 else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry")
614 {
615 string component = (string) row["ProductRegistryComponentID"];
616 return String.Format(@"Products\{0}\Components\{1}", path[1], component);
617 }
618 else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing")
619 {
620 string product = (string) row["ComponentProductsProductCode"];
621 return String.Format(@"Products\{0}\Components\{1}", MsiUtils.GetProductName(product), path[3]);
622 }
623 return null;
624 }
625 }
626}
diff --git a/src/samples/Dtf/Inventory/msiutils.cs b/src/samples/Dtf/Inventory/msiutils.cs
new file mode 100644
index 00000000..a345e194
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
9{
10 public class MsiUtils
11 {
12 private static Hashtable productCodesToNames = new Hashtable();
13 private static Hashtable productNamesToCodes = new Hashtable();
14
15 public static string GetProductName(string productCode)
16 {
17 string productName = (string) productCodesToNames[productCode];
18 if(productName == null)
19 {
20 productName = new ProductInstallation(productCode).ProductName;
21 productName = productName.Replace('\\', ' ');
22 if(productNamesToCodes.Contains(productName))
23 {
24 string modifiedProductName = null;
25 for(int i = 2; i < Int32.MaxValue; i++)
26 {
27 modifiedProductName = productName + " [" + i + "]";
28 if(!productNamesToCodes.Contains(modifiedProductName)) break;
29 }
30 productName = modifiedProductName;
31 }
32 productCodesToNames[productCode] = productName;
33 productNamesToCodes[productName] = productCode;
34 }
35 return productName;
36 }
37
38 // Assumes GetProductName() has already been called for this product.
39 public static string GetProductCode(string productName)
40 {
41 return (string) productNamesToCodes[productName];
42 }
43
44 private MsiUtils() { }
45 }
46}
diff --git a/src/samples/Dtf/Inventory/patches.cs b/src/samples/Dtf/Inventory/patches.cs
new file mode 100644
index 00000000..f01a4798
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
13{
14 /// <summary>
15 /// Provides inventory data about patches installed on the system.
16 /// </summary>
17 public class PatchesInventory : IInventoryDataProvider
18 {
19 public PatchesInventory()
20 {
21 }
22
23 public string Description
24 {
25 get { return "Installed patches"; }
26 }
27
28 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
29 {
30 ArrayList nodes = new ArrayList();
31 statusCallback(nodes.Count, @"Products\...\Patches");
32 foreach (ProductInstallation product in ProductInstallation.AllProducts)
33 {
34 string productName = MsiUtils.GetProductName(product.ProductCode);
35
36 bool addedRoot = false;
37 foreach (PatchInstallation productPatch in PatchInstallation.GetPatches(null, product.ProductCode, null, UserContexts.All, PatchStates.Applied))
38 {
39 if (!addedRoot) nodes.Add(String.Format(@"Products\{0}\Patches", productName));
40 nodes.Add(String.Format(@"Products\{0}\Patches\{1}", productName, productPatch.PatchCode));
41 }
42 }
43
44 statusCallback(nodes.Count, "Patches");
45
46 string[] allPatches = GetAllPatchesList();
47 if(allPatches.Length > 0)
48 {
49 nodes.Add("Patches");
50 foreach(string patchCode in allPatches)
51 {
52 nodes.Add(String.Format(@"Patches\{0}", patchCode));
53 nodes.Add(String.Format(@"Patches\{0}\Patched Products", patchCode));
54 }
55 statusCallback(nodes.Count, String.Empty);
56 }
57 return (string[]) nodes.ToArray(typeof(string));
58 }
59
60 public bool IsNodeSearchable(string searchRoot, string searchNode)
61 {
62 return true;
63 }
64
65 public DataView GetData(string nodePath)
66 {
67 string[] path = nodePath.Split('\\');
68
69 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
70 {
71 return this.GetProductPatchData(path[1]);
72 }
73 else if(path.Length == 4 && path[0] == "Products" && path[2] == "Patches")
74 {
75 return this.GetPatchData(path[3]);
76 }
77 else if(path.Length == 1 && path[0] == "Patches")
78 {
79 return this.GetAllPatchesData();
80 }
81 else if(path.Length == 2 && path[0] == "Patches")
82 {
83 return this.GetPatchData(path[1]);
84 }
85 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
86 {
87 return this.GetPatchTargetData(path[1]);
88 }
89 return null;
90 }
91
92 private string[] GetAllPatchesList()
93 {
94 ArrayList patchList = new ArrayList();
95 foreach(PatchInstallation patch in PatchInstallation.AllPatches)
96 {
97 if(!patchList.Contains(patch.PatchCode))
98 {
99 patchList.Add(patch.PatchCode);
100 }
101 }
102 string[] patchArray = (string[]) patchList.ToArray(typeof(string));
103 Array.Sort(patchArray, 0, patchArray.Length, StringComparer.Ordinal);
104 return patchArray;
105 }
106
107 private DataView GetAllPatchesData()
108 {
109 DataTable table = new DataTable("Patches");
110 table.Locale = CultureInfo.InvariantCulture;
111 table.Columns.Add("PatchesPatchCode", typeof(string));
112
113 foreach(string patchCode in GetAllPatchesList())
114 {
115 table.Rows.Add(new object[] { patchCode });
116 }
117 return new DataView(table, "", "PatchesPatchCode ASC", DataViewRowState.CurrentRows);
118 }
119
120 private DataView GetProductPatchData(string productCode)
121 {
122 DataTable table = new DataTable("ProductPatches");
123 table.Locale = CultureInfo.InvariantCulture;
124 table.Columns.Add("ProductPatchesPatchCode", typeof(string));
125
126 foreach(PatchInstallation patch in PatchInstallation.GetPatches(null, productCode, null, UserContexts.All, PatchStates.Applied))
127 {
128 table.Rows.Add(new object[] { patch.PatchCode });
129 }
130 return new DataView(table, "", "ProductPatchesPatchCode ASC", DataViewRowState.CurrentRows);
131 }
132
133 private DataView GetPatchData(string patchCode)
134 {
135 DataTable table = new DataTable("PatchProperties");
136 table.Locale = CultureInfo.InvariantCulture;
137 table.Columns.Add("PatchPropertiesProperty", typeof(string));
138 table.Columns.Add("PatchPropertiesValue", typeof(string));
139
140 table.Rows.Add(new object[] { "PatchCode", patchCode });
141
142 PatchInstallation patch = new PatchInstallation(patchCode, null);
143
144 string localPackage = null;
145 foreach(string property in new string[]
146 {
147 "InstallDate",
148 "LocalPackage",
149 "State",
150 "Transforms",
151 "Uninstallable",
152 })
153 {
154 try
155 {
156 string value = patch[property];
157 table.Rows.Add(new object[] { property, (value != null ? value : "") });
158 if(property == "LocalPackage") localPackage = value;
159 }
160 catch(InstallerException iex)
161 {
162 table.Rows.Add(new object[] { property, iex.Message });
163 }
164 catch(ArgumentException) { }
165 }
166
167 if(localPackage != null)
168 {
169 try
170 {
171 using(SummaryInfo patchSummaryInfo = new SummaryInfo(localPackage, false))
172 {
173 table.Rows.Add(new object[] { "Title", patchSummaryInfo.Title });
174 table.Rows.Add(new object[] { "Subject", patchSummaryInfo.Subject });
175 table.Rows.Add(new object[] { "Author", patchSummaryInfo.Author });
176 table.Rows.Add(new object[] { "Comments", patchSummaryInfo.Comments });
177 table.Rows.Add(new object[] { "TargetProductCodes", patchSummaryInfo.Template });
178 string obsoletedPatchCodes = patchSummaryInfo.RevisionNumber.Substring(patchSummaryInfo.RevisionNumber.IndexOf('}') + 1);
179 table.Rows.Add(new object[] { "ObsoletedPatchCodes", obsoletedPatchCodes });
180 table.Rows.Add(new object[] { "TransformNames", patchSummaryInfo.LastSavedBy });
181 }
182 }
183 catch(InstallerException) { }
184 catch(IOException) { }
185 catch(SecurityException) { }
186 }
187 return new DataView(table, "", "PatchPropertiesProperty ASC", DataViewRowState.CurrentRows);
188 }
189
190 private DataView GetPatchTargetData(string patchCode)
191 {
192 DataTable table = new DataTable("PatchTargets");
193 table.Locale = CultureInfo.InvariantCulture;
194 table.Columns.Add("PatchTargetsProductName", typeof(string));
195 table.Columns.Add("PatchTargetsProductCode", typeof(string));
196
197 foreach (PatchInstallation patch in PatchInstallation.GetPatches(patchCode, null, null, UserContexts.All, PatchStates.Applied))
198 {
199 if(patch.PatchCode == patchCode)
200 {
201 string productName = MsiUtils.GetProductName(patch.ProductCode);
202 table.Rows.Add(new object[] { productName, patch.ProductCode });
203 }
204 }
205 return new DataView(table, "", "PatchTargetsProductName ASC", DataViewRowState.CurrentRows);
206 }
207
208 public string GetLink(string nodePath, DataRow row)
209 {
210 string[] path = nodePath.Split('\\');
211
212 if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches")
213 {
214 return String.Format(@"Patches\{0}", row["ProductPatchesPatchCode"]);
215 }
216 else if(path.Length == 1 && path[0] == "Patches")
217 {
218 return String.Format(@"Patches\{0}", row["PatchesPatchCode"]);
219 }
220 else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products")
221 {
222 return String.Format(@"Products\{0}", MsiUtils.GetProductCode((string) row["PatchTargetsProductCode"]));
223 }
224 return null;
225 }
226 }
227}
diff --git a/src/samples/Dtf/Inventory/products.cs b/src/samples/Dtf/Inventory/products.cs
new file mode 100644
index 00000000..872c56c3
--- /dev/null
+++ b/src/samples/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.Samples.Inventory
11{
12 /// <summary>
13 /// Provides inventory data about products installed or advertised on the system.
14 /// </summary>
15 public class ProductsInventory : IInventoryDataProvider
16 {
17 public ProductsInventory()
18 {
19 }
20
21 public string Description
22 {
23 get { return "Installed products"; }
24 }
25
26 public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback)
27 {
28 statusCallback(0, "Products");
29 ArrayList nodes = new ArrayList();
30 nodes.Add("Products");
31 foreach(ProductInstallation product in ProductInstallation.AllProducts)
32 {
33 nodes.Add("Products\\" + MsiUtils.GetProductName(product.ProductCode));
34 }
35 statusCallback(nodes.Count, String.Empty);
36 return (string[]) nodes.ToArray(typeof(string));
37 }
38
39 public bool IsNodeSearchable(string searchRoot, string searchNode)
40 {
41 return true;
42 }
43
44 public DataView GetData(string nodePath)
45 {
46 string[] path = nodePath.Split('\\');
47
48 if(path.Length == 1 && path[0] == "Products")
49 {
50 return this.GetAllProductsData();
51 }
52 else if(path.Length == 2 && path[0] == "Products")
53 {
54 return this.GetProductData(MsiUtils.GetProductCode(path[1]));
55 }
56 return null;
57 }
58
59 private DataView GetAllProductsData()
60 {
61 DataTable table = new DataTable("Products");
62 table.Locale = CultureInfo.InvariantCulture;
63 table.Columns.Add("ProductsProductName", typeof(string));
64 table.Columns.Add("ProductsProductCode", typeof(string));
65
66 foreach (ProductInstallation product in ProductInstallation.AllProducts)
67 {
68 string productName = MsiUtils.GetProductName(product.ProductCode);
69 table.Rows.Add(new object[] { productName, product.ProductCode });
70 }
71 return new DataView(table, "", "ProductsProductName ASC", DataViewRowState.CurrentRows);
72 }
73
74 private DataView GetProductData(string productCode)
75 {
76 DataTable table = new DataTable("ProductProperties");
77 table.Locale = CultureInfo.InvariantCulture;
78 table.Columns.Add("ProductPropertiesProperty", typeof(string));
79 table.Columns.Add("ProductPropertiesValue", typeof(string));
80
81 // Add a fake "ProductCode" install property, just for display convenience.
82 table.Rows.Add(new object[] { "ProductCode", productCode });
83
84 ProductInstallation product = new ProductInstallation(productCode);
85
86 foreach(string property in new string[]
87 {
88 "AssignmentType",
89 "DiskPrompt",
90 "HelpLink",
91 "HelpTelephone",
92 "InstalledProductName",
93 "InstallDate",
94 "InstallLocation",
95 "InstallSource",
96 "Language",
97 "LastUsedSource",
98 "LastUsedType",
99 "LocalPackage",
100 "MediaPackagePath",
101 "PackageCode",
102 "PackageName",
103 "ProductIcon",
104 "ProductID",
105 "ProductName",
106 "Publisher",
107 "RegCompany",
108 "RegOwner",
109 "State",
110 "transforms",
111 "Uninstallable",
112 "UrlInfoAbout",
113 "UrlUpdateInfo",
114 "Version",
115 "VersionMinor",
116 "VersionMajor",
117 "VersionString"
118 })
119 {
120 try
121 {
122 string value = product[property];
123 table.Rows.Add(new object[] { property, (value != null ? value : "") });
124 }
125 catch(InstallerException iex)
126 {
127 table.Rows.Add(new object[] { property, iex.Message });
128 }
129 catch(ArgumentException) { }
130 }
131 return new DataView(table, "", "ProductPropertiesProperty ASC", DataViewRowState.CurrentRows);
132 }
133
134 public string GetLink(string nodePath, DataRow row)
135 {
136 string[] path = nodePath.Split('\\');
137
138 if(path.Length == 1 && path[0] == "Products")
139 {
140 return String.Format(@"Products\{0}", MsiUtils.GetProductName((string) row["ProductsProductCode"]));
141 }
142 return null;
143 }
144 }
145}
diff --git a/src/samples/Dtf/Inventory/xp.manifest b/src/samples/Dtf/Inventory/xp.manifest
new file mode 100644
index 00000000..34d61fea
--- /dev/null
+++ b/src/samples/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/samples/Dtf/ManagedCA/AssemblyInfo.cs b/src/samples/Dtf/ManagedCA/AssemblyInfo.cs
new file mode 100644
index 00000000..75be36b2
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/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("Sample managed custom actions")]
diff --git a/src/samples/Dtf/ManagedCA/ManagedCA.csproj b/src/samples/Dtf/ManagedCA/ManagedCA.csproj
new file mode 100644
index 00000000..7fb32ad4
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/ManagedCA.csproj
@@ -0,0 +1,33 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <PropertyGroup>
4 <ProjectGuid>{DB9E5F02-8241-440A-9B60-980EB5B42B13}</ProjectGuid>
5 <OutputType>Library</OutputType>
6 <RootNamespace>WixToolset.Dtf.Samples.ManagedCA</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Samples.ManagedCA</AssemblyName>
8 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
9 <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
10 </PropertyGroup>
11 <ItemGroup>
12 <Compile Include="AssemblyInfo.cs" />
13 <Compile Include="SampleCAs.cs" />
14 </ItemGroup>
15 <ItemGroup>
16 <Reference Include="System" />
17 </ItemGroup>
18 <ItemGroup>
19 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
20 <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
21 <Name>WixToolset.Dtf.WindowsInstaller</Name>
22 </ProjectReference>
23 </ItemGroup>
24
25 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
26
27<!--
28 <PropertyGroup>
29 <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" WixToolset.Dtf.WindowsInstaller.dll="$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll" testsub\SampleCAs.cs="$(ProjectDir)\SampleCAs.cs"</PostBuildEvent>
30 </PropertyGroup>
31-->
32
33</Project>
diff --git a/src/samples/Dtf/ManagedCA/SampleCAs.cs b/src/samples/Dtf/ManagedCA/SampleCAs.cs
new file mode 100644
index 00000000..645131c8
--- /dev/null
+++ b/src/samples/Dtf/ManagedCA/SampleCAs.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
3namespace WixToolset.Dtf.Samples.ManagedCA
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Dtf.WindowsInstaller;
9
10 public class SampleCAs
11 {
12 [CustomAction]
13 public static ActionResult SampleCA1(Session session)
14 {
15 using (Record msgRec = new Record(0))
16 {
17 msgRec[0] = "Hello from SampleCA1!" +
18 "\r\nCLR version is v" + Environment.Version;
19 session.Message(InstallMessage.Info, msgRec);
20 session.Message(InstallMessage.User, msgRec);
21 }
22
23 session.Log("Testing summary info...");
24 SummaryInfo summInfo = session.Database.SummaryInfo;
25 session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber);
26 session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime);
27
28 string testProp = session["SampleCATest"];
29 session.Log("Simple property test: [SampleCATest]={0}.", testProp);
30
31 session.Log("Testing subdirectory extraction...");
32 string testFilePath = "testsub\\SampleCAs.cs";
33 if (!File.Exists(testFilePath))
34 {
35 session.Log("Subdirectory extraction failed. File not found: " + testFilePath);
36 return ActionResult.Failure;
37 }
38 else
39 {
40 session.Log("Found file extracted in subdirectory.");
41 }
42
43 session.Log("Testing record stream extraction...");
44 string tempFile = null;
45 try
46 {
47 tempFile = Path.GetTempFileName();
48 using (View binView = session.Database.OpenView(
49 "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " +
50 "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " +
51 "`CustomAction`.`Source` = `Binary`.`Name`"))
52 {
53 binView.Execute();
54 using (Record binRec = binView.Fetch())
55 {
56 binRec.GetStream(1, tempFile);
57 }
58 }
59
60 session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length);
61 string binFileVersion = Installer.GetFileVersion(tempFile);
62 session.Log("CA binary file version: {0}", binFileVersion);
63 }
64 finally
65 {
66 if (tempFile != null && File.Exists(tempFile))
67 {
68 File.Delete(tempFile);
69 }
70 }
71
72 session.Log("Testing record stream reading...");
73 using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'"))
74 {
75 binView2.Execute();
76 using (Record binRec2 = binView2.Fetch())
77 {
78 Stream stream = binRec2.GetStream("Data");
79 string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd();
80 session.Log("Test data: " + testData);
81 }
82 }
83
84 session.Log("Listing components");
85 using (View compView = session.Database.OpenView(
86 "SELECT `Component` FROM `Component`"))
87 {
88 compView.Execute();
89 foreach (Record compRec in compView)
90 {
91 using (compRec)
92 {
93 session.Log("\t{0}", compRec["Component"]);
94 }
95 }
96 }
97
98 session.Log("Testing the ability to access an external MSI database...");
99 string tempDbFile = Path.GetTempFileName();
100 using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect))
101 {
102 // Just create an empty database.
103 }
104 using (Database tempDb2 = new Database(tempDbFile))
105 {
106 // See if we can open and query the database.
107 IList<string> tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`");
108 session.Log("Found " + tables.Count + " tables in the newly created database.");
109 }
110 File.Delete(tempDbFile);
111
112 return ActionResult.Success;
113 }
114
115 [CustomAction("SampleCA2")]
116 public static ActionResult SampleCustomAction2(Session session)
117 {
118 using (Record msgRec = new Record(0))
119 {
120 msgRec[0] = "Hello from SampleCA2!";
121 session.Message(InstallMessage.Info, msgRec);
122 session.Message(InstallMessage.User, msgRec);
123 }
124 return ActionResult.UserExit;
125 }
126 }
127}
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs
new file mode 100644
index 00000000..76ff79b3
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.cs
@@ -0,0 +1,711 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Tools.MakeSfxCA
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Security;
9 using System.Text;
10 using System.Reflection;
11 using Compression;
12 using Compression.Cab;
13 using Resources;
14 using ResourceCollection = Resources.ResourceCollection;
15
16 /// <summary>
17 /// Command-line tool for building self-extracting custom action packages.
18 /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's
19 /// entry-points and file version to look like the CA module.
20 /// </summary>
21 public static class MakeSfxCA
22 {
23 private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll";
24
25 private static TextWriter log;
26
27 /// <summary>
28 /// Prints usage text for the tool.
29 /// </summary>
30 /// <param name="w">Console text writer.</param>
31 public static void Usage(TextWriter w)
32 {
33 w.WriteLine("Deployment Tools Foundation custom action packager version {0}",
34 Assembly.GetExecutingAssembly().GetName().Version);
35 w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved.");
36 w.WriteLine();
37 w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]");
38 w.WriteLine();
39 w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package.");
40 w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY);
41 w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config");
42 }
43
44 /// <summary>
45 /// Runs the MakeSfxCA command-line tool.
46 /// </summary>
47 /// <param name="args">Command-line arguments.</param>
48 /// <returns>0 on success, nonzero on failure.</returns>
49 public static int Main(string[] args)
50 {
51 if (args.Length < 3)
52 {
53 Usage(Console.Out);
54 return 1;
55 }
56
57 var output = args[0];
58 var sfxDll = args[1];
59 var inputs = new string[args.Length - 2];
60 Array.Copy(args, 2, inputs, 0, inputs.Length);
61
62 try
63 {
64 Build(output, sfxDll, inputs, Console.Out);
65 return 0;
66 }
67 catch (ArgumentException ex)
68 {
69 Console.Error.WriteLine("Error: Invalid argument: " + ex.Message);
70 return 1;
71 }
72 catch (FileNotFoundException ex)
73 {
74 Console.Error.WriteLine("Error: Cannot find file: " + ex.Message);
75 return 1;
76 }
77 catch (Exception ex)
78 {
79 Console.Error.WriteLine("Error: Unexpected error: " + ex);
80 return 1;
81 }
82 }
83
84 /// <summary>
85 /// Packages up all the inputs to the output location.
86 /// </summary>
87 /// <exception cref="Exception">Various exceptions are thrown
88 /// if things go wrong.</exception>
89 public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log)
90 {
91 MakeSfxCA.log = log;
92
93 if (string.IsNullOrEmpty(output))
94 {
95 throw new ArgumentNullException("output");
96 }
97
98 if (string.IsNullOrEmpty(sfxDll))
99 {
100 throw new ArgumentNullException("sfxDll");
101 }
102
103 if (inputs == null || inputs.Count == 0)
104 {
105 throw new ArgumentNullException("inputs");
106 }
107
108 if (!File.Exists(sfxDll))
109 {
110 throw new FileNotFoundException(sfxDll);
111 }
112
113 var customActionAssembly = inputs[0];
114 if (!File.Exists(customActionAssembly))
115 {
116 throw new FileNotFoundException(customActionAssembly);
117 }
118
119 inputs = MakeSfxCA.SplitList(inputs);
120
121 var inputsMap = MakeSfxCA.GetPackFileMap(inputs);
122
123 var foundWIAssembly = false;
124 foreach (var input in inputsMap.Keys)
125 {
126 if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY,
127 StringComparison.OrdinalIgnoreCase) == 0)
128 {
129 foundWIAssembly = true;
130 }
131 }
132
133 if (!foundWIAssembly)
134 {
135 throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY +
136 " must be included in the list of support files. " +
137 "If using the MSBuild targets, make sure the assembly reference " +
138 "has the Private (Copy Local) flag set.");
139 }
140
141 MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly));
142
143 var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly);
144 var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly);
145
146 if (entryPoints.Count == 0 && uiClass == null)
147 {
148 throw new ArgumentException(
149 "No CA or UI entry points found in module: " + customActionAssembly);
150 }
151 else if (entryPoints.Count > 0 && uiClass != null)
152 {
153 throw new NotSupportedException(
154 "CA and UI entry points cannot be in the same assembly: " + customActionAssembly);
155 }
156
157 var dir = Path.GetDirectoryName(output);
158 if (dir.Length > 0 && !Directory.Exists(dir))
159 {
160 Directory.CreateDirectory(dir);
161 }
162
163 using (Stream outputStream = File.Create(output))
164 {
165 MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass);
166 }
167
168 MakeSfxCA.CopyVersionResource(customActionAssembly, output);
169
170 MakeSfxCA.PackInputFiles(output, inputsMap);
171
172 log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName);
173 }
174
175 /// <summary>
176 /// Splits any list items delimited by semicolons into separate items.
177 /// </summary>
178 /// <param name="list">Read-only input list.</param>
179 /// <returns>New list with resulting split items.</returns>
180 private static IList<string> SplitList(IList<string> list)
181 {
182 var newList = new List<string>(list.Count);
183
184 foreach (var item in list)
185 {
186 if (!string.IsNullOrEmpty(item))
187 {
188 foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
189 {
190 newList.Add(splitItem);
191 }
192 }
193 }
194
195 return newList;
196 }
197
198 /// <summary>
199 /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection.
200 /// </summary>
201 /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param>
202 /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param>
203 /// <remarks>
204 /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them
205 /// to the list of input files if found.
206 /// </remarks>
207 private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir)
208 {
209 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args)
210 {
211 AssemblyName resolveName = new AssemblyName(args.Name);
212 Assembly assembly = null;
213
214 // First, try to find the assembly in the list of input files.
215 foreach (var inputFile in inputFiles.Values)
216 {
217 var inputName = Path.GetFileNameWithoutExtension(inputFile);
218 var inputExtension = Path.GetExtension(inputFile);
219 if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) &&
220 (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) ||
221 string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase)))
222 {
223 assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile);
224
225 if (assembly != null)
226 {
227 break;
228 }
229 }
230 }
231
232 // Second, try to find the assembly in the input directory.
233 if (assembly == null && inputDir != null)
234 {
235 string assemblyPath = null;
236 if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll"))
237 {
238 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll";
239 }
240 else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe"))
241 {
242 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe";
243 }
244
245 if (assemblyPath != null)
246 {
247 assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath);
248
249 if (assembly != null)
250 {
251 // Add this detected dependency to the list of files to be packed.
252 inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath);
253 }
254 }
255 }
256
257 // Third, try to load the assembly from the GAC.
258 if (assembly == null)
259 {
260 try
261 {
262 assembly = Assembly.ReflectionOnlyLoad(args.Name);
263 }
264 catch (FileNotFoundException)
265 {
266 }
267 }
268
269 if (assembly != null)
270 {
271 if (string.Equals(assembly.GetName().ToString(), resolveName.ToString()))
272 {
273 log.WriteLine(" Loaded dependent assembly: " + assembly.Location);
274 return assembly;
275 }
276
277 log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location);
278 log.WriteLine(" Loaded assembly : " + assembly.GetName());
279 log.WriteLine(" Reference assembly: " + resolveName);
280 }
281 else
282 {
283 log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName);
284 }
285
286 return null;
287 };
288 }
289
290 /// <summary>
291 /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails.
292 /// </summary>
293 /// <param name="assemblyPath">Path of the assembly file to laod.</param>
294 /// <returns>Loaded assembly, or null if the load failed.</returns>
295 private static Assembly TryLoadDependentAssembly(string assemblyPath)
296 {
297 Assembly assembly = null;
298 try
299 {
300 assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
301 }
302 catch (IOException ex)
303 {
304 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
305 }
306 catch (BadImageFormatException ex)
307 {
308 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
309 }
310 catch (SecurityException ex)
311 {
312 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
313 }
314
315 return assembly;
316 }
317
318 /// <summary>
319 /// Searches the types in the input assembly for a type that implements IEmbeddedUI.
320 /// </summary>
321 /// <param name="module"></param>
322 /// <returns></returns>
323 private static string FindEmbeddedUIClass(string module)
324 {
325 log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module));
326
327 string uiClass = null;
328
329 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
330
331 foreach (var type in assembly.GetExportedTypes())
332 {
333 if (!type.IsAbstract)
334 {
335 foreach (var interfaceType in type.GetInterfaces())
336 {
337 if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI")
338 {
339 if (uiClass == null)
340 {
341 uiClass = assembly.GetName().Name + "!" + type.FullName;
342 }
343 else
344 {
345 throw new ArgumentException("Multiple IEmbeddedUI implementations found.");
346 }
347 }
348 }
349 }
350 }
351
352 return uiClass;
353 }
354
355 /// <summary>
356 /// Reflects on an input CA module to locate custom action entry-points.
357 /// </summary>
358 /// <param name="module">Assembly module with CA entry-points.</param>
359 /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns>
360 private static IDictionary<string, string> FindEntryPoints(string module)
361 {
362 log.WriteLine("Searching for custom action entry points " +
363 "in {0}", Path.GetFileName(module));
364
365 var entryPoints = new Dictionary<string, string>();
366
367 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
368
369 foreach (var type in assembly.GetExportedTypes())
370 {
371 foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
372 {
373 var entryPointName = MakeSfxCA.GetEntryPoint(method);
374 if (entryPointName != null)
375 {
376 var entryPointPath = string.Format(
377 "{0}!{1}.{2}",
378 Path.GetFileNameWithoutExtension(module),
379 type.FullName,
380 method.Name);
381 entryPoints.Add(entryPointName, entryPointPath);
382
383 log.WriteLine(" {0}={1}", entryPointName, entryPointPath);
384 }
385 }
386 }
387
388 return entryPoints;
389 }
390
391 /// <summary>
392 /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method.
393 /// </summary>
394 /// <param name="method">A public static method.</param>
395 /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name,
396 /// or null if the method is not a custom action method.</returns>
397 private static string GetEntryPoint(MethodInfo method)
398 {
399 IList<CustomAttributeData> attributes;
400 try
401 {
402 attributes = CustomAttributeData.GetCustomAttributes(method);
403 }
404 catch (FileLoadException)
405 {
406 // Already logged load failures in the assembly-resolve-handler.
407 return null;
408 }
409
410 foreach (CustomAttributeData attribute in attributes)
411 {
412 if (attribute.ToString().StartsWith(
413 "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(",
414 StringComparison.Ordinal))
415 {
416 string entryPointName = null;
417 foreach (var argument in attribute.ConstructorArguments)
418 {
419 // The entry point name is the first positional argument, if specified.
420 entryPointName = (string) argument.Value;
421 break;
422 }
423
424 if (string.IsNullOrEmpty(entryPointName))
425 {
426 entryPointName = method.Name;
427 }
428
429 return entryPointName;
430 }
431 }
432
433 return null;
434 }
435
436 /// <summary>
437 /// Counts the number of template entrypoints in SfxCA.dll.
438 /// </summary>
439 /// <remarks>
440 /// Depending on the requirements, SfxCA.dll might be built with
441 /// more entrypoints than the default.
442 /// </remarks>
443 private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat)
444 {
445 for (var count = 0; ; count++)
446 {
447 var templateName = string.Format(entryPointFormat, count);
448 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
449
450 var nameOffset = FindBytes(fileBytes, templateAsciiBytes);
451 if (nameOffset < 0)
452 {
453 return count;
454 }
455 }
456 }
457
458 /// <summary>
459 /// Writes a modified version of SfxCA.dll to the output stream,
460 /// with the template entry-points mapped to the CA entry-points.
461 /// </summary>
462 /// <remarks>
463 /// To avoid having to recompile SfxCA.dll for every different set of CAs,
464 /// this method looks for a preset number of template entry-points in the
465 /// binary file and overwrites their entrypoint name and string data with
466 /// CA-specific values.
467 /// </remarks>
468 private static void WriteEntryModule(
469 string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass)
470 {
471 log.WriteLine("Modifying SfxCA.dll stub");
472
473 byte[] fileBytes;
474 using (var readStream = File.OpenRead(sfxDll))
475 {
476 fileBytes = new byte[(int) readStream.Length];
477 readStream.Read(fileBytes, 0, fileBytes.Length);
478 }
479
480 const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}";
481 const int MAX_ENTRYPOINT_NAME = 72;
482 const int MAX_ENTRYPOINT_PATH = 160;
483 //var emptyBytes = new byte[0];
484
485 var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT);
486
487 if (slotCount == 0)
488 {
489 throw new ArgumentException("Invalid SfxCA.dll file.");
490 }
491
492 if (entryPoints.Count > slotCount)
493 {
494 throw new ArgumentException(string.Format(
495 "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " +
496 "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.",
497 entryPoints.Count, slotCount));
498 }
499
500 var slotSort = new string[slotCount];
501 for (var i = 0; i < slotCount - entryPoints.Count; i++)
502 {
503 slotSort[i] = string.Empty;
504 }
505
506 entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count);
507 Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal);
508
509 for (var i = 0; ; i++)
510 {
511 var templateName = string.Format(ENTRYPOINT_FORMAT, i);
512 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
513 var templateUniBytes = Encoding.Unicode.GetBytes(templateName);
514
515 var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes);
516 if (nameOffset < 0)
517 {
518 break;
519 }
520
521 var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes);
522 if (pathOffset < 0)
523 {
524 break;
525 }
526
527 var entryPointName = slotSort[i];
528 var entryPointPath = entryPointName.Length > 0 ?
529 entryPoints[entryPointName] : string.Empty;
530
531 if (entryPointName.Length > MAX_ENTRYPOINT_NAME)
532 {
533 throw new ArgumentException(string.Format(
534 "Entry point name exceeds limit of {0} characters: {1}",
535 MAX_ENTRYPOINT_NAME,
536 entryPointName));
537 }
538
539 if (entryPointPath.Length > MAX_ENTRYPOINT_PATH)
540 {
541 throw new ArgumentException(string.Format(
542 "Entry point path exceeds limit of {0} characters: {1}",
543 MAX_ENTRYPOINT_PATH,
544 entryPointPath));
545 }
546
547 var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName);
548 var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath);
549
550 MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes);
551 MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes);
552 }
553
554 if (entryPoints.Count == 0 && uiClass != null)
555 {
556 // Remove the zzz prefix from exported EmbeddedUI entry-points.
557 foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" })
558 {
559 var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export);
560
561 var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes);
562 if (exportOffset < 0)
563 {
564 throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export);
565 }
566
567 var replaceNameBytes = Encoding.ASCII.GetBytes(export);
568 MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes);
569 }
570
571 if (uiClass.Length > MAX_ENTRYPOINT_PATH)
572 {
573 throw new ArgumentException(string.Format(
574 "UI class full name exceeds limit of {0} characters: {1}",
575 MAX_ENTRYPOINT_PATH,
576 uiClass));
577 }
578
579 var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName");
580 var replaceBytes = Encoding.Unicode.GetBytes(uiClass);
581
582 // Fill in the embedded UI implementor class so the proxy knows which one to load.
583 var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes);
584 if (replaceOffset >= 0)
585 {
586 MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes);
587 }
588 }
589
590 outputStream.Write(fileBytes, 0, fileBytes.Length);
591 }
592
593 /// <summary>
594 /// Searches for a sub-array of bytes within a larger array of bytes.
595 /// </summary>
596 private static int FindBytes(byte[] source, byte[] find)
597 {
598 for (var i = 0; i < source.Length; i++)
599 {
600 int j;
601 for (j = 0; j < find.Length; j++)
602 {
603 if (source[i + j] != find[j])
604 {
605 break;
606 }
607 }
608
609 if (j == find.Length)
610 {
611 return i;
612 }
613 }
614
615 return -1;
616 }
617
618 /// <summary>
619 /// Replaces a range of bytes with new bytes, padding any extra part
620 /// of the range with zeroes.
621 /// </summary>
622 private static void ReplaceBytes(
623 byte[] source, int offset, int length, byte[] replace)
624 {
625 for (var i = 0; i < length; i++)
626 {
627 if (i < replace.Length)
628 {
629 source[offset + i] = replace[i];
630 }
631 else
632 {
633 source[offset + i] = 0;
634 }
635 }
636 }
637
638 /// <summary>
639 /// Print the name of one file as it is being packed into the cab.
640 /// </summary>
641 private static void PackProgress(object source, ArchiveProgressEventArgs e)
642 {
643 if (e.ProgressType == ArchiveProgressType.StartFile && log != null)
644 {
645 log.WriteLine(" {0}", e.CurrentFileName);
646 }
647 }
648
649 /// <summary>
650 /// Gets a mapping from filenames as they will be in the cab to filenames
651 /// as they are currently on disk.
652 /// </summary>
653 /// <remarks>
654 /// By default, all files will be placed in the root of the cab. But inputs may
655 /// optionally include an alternate inside-cab file path before an equals sign.
656 /// </remarks>
657 private static IDictionary<string, string> GetPackFileMap(IList<string> inputs)
658 {
659 var fileMap = new Dictionary<string, string>();
660 foreach (var inputFile in inputs)
661 {
662 if (inputFile.IndexOf('=') > 0)
663 {
664 var parse = inputFile.Split('=');
665 if (!fileMap.ContainsKey(parse[0]))
666 {
667 fileMap.Add(parse[0], parse[1]);
668 }
669 }
670 else
671 {
672 var fileName = Path.GetFileName(inputFile);
673 if (!fileMap.ContainsKey(fileName))
674 {
675 fileMap.Add(fileName, inputFile);
676 }
677 }
678 }
679 return fileMap;
680 }
681
682 /// <summary>
683 /// Packs the input files into a cab that is appended to the
684 /// output SfxCA.dll.
685 /// </summary>
686 private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap)
687 {
688 log.WriteLine("Packaging files");
689
690 var cabInfo = new CabInfo(outputFile);
691 cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress);
692 }
693
694 /// <summary>
695 /// Copies the version resource information from the CA module to
696 /// the CA package. This gives the package the file version and
697 /// description of the CA module, instead of the version and
698 /// description of SfxCA.dll.
699 /// </summary>
700 private static void CopyVersionResource(string sourceFile, string destFile)
701 {
702 log.WriteLine("Copying file version info from {0} to {1}",
703 sourceFile, destFile);
704
705 var rc = new ResourceCollection();
706 rc.Find(sourceFile, ResourceType.Version);
707 rc.Load(sourceFile);
708 rc.Save(destFile);
709 }
710 }
711}
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj
new file mode 100644
index 00000000..c6982532
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.csproj
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netcoreapp3.1;net461</TargetFrameworks>
7 <OutputType>Exe</OutputType>
8 <RootNamespace>WixToolset.Dtf.Tools.MakeSfxCA</RootNamespace>
9 <AssemblyName>MakeSfxCA</AssemblyName>
10 <DebugType>embedded</DebugType>
11 <AppConfig>app.config</AppConfig>
12 <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest>
13 <RollForward>Major</RollForward>
14 <RuntimeIdentifier>win-x86</RuntimeIdentifier>
15 </PropertyGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
19 </ItemGroup>
20
21 <ItemGroup>
22 <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
23 <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
24 <ProjectReference Include="..\..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" />
25 </ItemGroup>
26
27 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
28 <PropertyGroup>
29 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
30 </PropertyGroup>
31 <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
32 </Target>
33</Project>
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest
new file mode 100644
index 00000000..49b074e0
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/MakeSfxCA.exe.manifest
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="WixToolset.Dtf.Tools.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Compiler</description>
8 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
9 <security>
10 <requestedPrivileges>
11 <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
12 </requestedPrivileges>
13 </security>
14 </trustInfo>
15 <application xmlns="urn:schemas-microsoft-com:asm.v3">
16 <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
17 <ws2:longPathAware>true</ws2:longPathAware>
18 </windowsSettings>
19 </application>
20</assembly>
diff --git a/src/samples/Dtf/Tools/MakeSfxCA/app.config b/src/samples/Dtf/Tools/MakeSfxCA/app.config
new file mode 100644
index 00000000..65d3d6c3
--- /dev/null
+++ b/src/samples/Dtf/Tools/MakeSfxCA/app.config
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<configuration>
6 <runtime>
7 <loadFromRemoteSources enabled="true"/>
8 <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
9 </runtime>
10</configuration>
diff --git a/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp
new file mode 100644
index 00000000..1988fb2a
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/ClrHost.cpp
@@ -0,0 +1,262 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7//---------------------------------------------------------------------
8// CLR HOSTING
9//---------------------------------------------------------------------
10
11/// <summary>
12/// Binds to the CLR after determining the appropriate version.
13/// </summary>
14/// <param name="hSession">Handle to the installer session,
15/// used just for logging.</param>
16/// <param name="version">Specific version of the CLR to load.
17/// If null, then the config file and/or primary assembly are
18/// used to determine the version.</param>
19/// <param name="szConfigFile">XML .config file which may contain
20/// a startup section to direct which version of the CLR to use.
21/// May be NULL.</param>
22/// <param name="szPrimaryAssembly">Assembly to be used to determine
23/// the version of the CLR in the absence of other configuration.
24/// May be NULL.</param>
25/// <param name="ppHost">Returned runtime host interface.</param>
26/// <returns>True if the CLR was loaded successfully, false if
27/// there was some error.</returns>
28/// <remarks>
29/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or
30/// does not contain any version configuration, the CLR will not be loaded.
31/// </remarks>
32bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
33 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost)
34{
35 typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion,
36 LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags,
37 LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength,
38 LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength);
39 typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor,
40 DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
41
42 HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll");
43 if (hmodMscoree == NULL)
44 {
45 Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action "
46 L"requires the .NET Framework to be installed.", GetLastError());
47 return false;
48 }
49 PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo)
50 GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo");
51 PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx)
52 GetProcAddress(hmodMscoree, "CorBindToRuntimeEx");
53 if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL)
54 {
55 Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action "
56 L"requires the .NET Framework to be installed.", GetLastError());
57 FreeLibrary(hmodMscoree);
58 return false;
59 }
60
61 wchar_t szClrVersion[20];
62 HRESULT hr;
63
64 if (szVersion != NULL && szVersion[0] != L'\0')
65 {
66 wcsncpy_s(szClrVersion, 20, szVersion, 20);
67 }
68 else
69 {
70 wchar_t szVersionDir[MAX_PATH];
71 hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL,
72 szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL);
73 if (FAILED(hr))
74 {
75 Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr);
76 Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or "
77 L"that there is a matching supportedRuntime element in CustomAction.config. "
78 L"If you are binding to .NET 4 or greater add "
79 L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element.");
80 FreeLibrary(hmodMscoree);
81 return false;
82 }
83 }
84
85 Log(hSession, L"Binding to CLR version %s", szClrVersion);
86
87 ICorRuntimeHost* pHost;
88 hr = pCorBindToRuntimeEx(szClrVersion, NULL,
89 STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN,
90 CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost);
91 if (FAILED(hr))
92 {
93 Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr);
94 FreeLibrary(hmodMscoree);
95 return false;
96 }
97 hr = pHost->Start();
98 if (FAILED(hr))
99 {
100 Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr);
101 pHost->Release();
102 FreeLibrary(hmodMscoree);
103 return false;
104 }
105 *ppHost = pHost;
106 FreeLibrary(hmodMscoree);
107 return true;
108}
109
110/// <summary>
111/// Creates a new CLR application domain.
112/// </summary>
113/// <param name="hSession">Handle to the installer session,
114/// used just for logging</param>
115/// <param name="pHost">Interface to the runtime host where the
116/// app domain will be created.</param>
117/// <param name="szName">Name of the app domain to create.</param>
118/// <param name="szAppBase">Application base directory path, where
119/// the app domain will look first to load its assemblies.</param>
120/// <param name="szConfigFile">Optional XML .config file containing any
121/// configuration for thae app domain.</param>
122/// <param name="ppAppDomain">Returned app domain interface.</param>
123/// <returns>True if the app domain was created successfully, false if
124/// there was some error.</returns>
125bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
126 const wchar_t* szName, const wchar_t* szAppBase,
127 const wchar_t* szConfigFile, _AppDomain** ppAppDomain)
128{
129 IUnknown* punkAppDomainSetup = NULL;
130 IAppDomainSetup* pAppDomainSetup = NULL;
131 HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup);
132 if (SUCCEEDED(hr))
133 {
134 hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup);
135 punkAppDomainSetup->Release();
136 }
137 if (FAILED(hr))
138 {
139 Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr);
140 return false;
141 }
142
143 const wchar_t* szUrlPrefix = L"file:///";
144 size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase);
145 wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t));
146 if (szApplicationBase == NULL) hr = E_OUTOFMEMORY;
147 else
148 {
149 StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix);
150 StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase);
151 BSTR bstrApplicationBase = SysAllocString(szApplicationBase);
152 if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY;
153 else
154 {
155 hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase);
156 SysFreeString(bstrApplicationBase);
157 }
158 }
159
160 if (SUCCEEDED(hr) && szConfigFile != NULL)
161 {
162 BSTR bstrConfigFile = SysAllocString(szConfigFile);
163 if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY;
164 else
165 {
166 hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile);
167 SysFreeString(bstrConfigFile);
168 }
169 }
170
171 if (FAILED(hr))
172 {
173 Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr);
174 pAppDomainSetup->Release();
175 return false;
176 }
177
178 IUnknown* punkAppDomain;
179 hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain);
180 pAppDomainSetup->Release();
181 if (SUCCEEDED(hr))
182 {
183 hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain);
184 punkAppDomain->Release();
185 }
186
187 if (FAILED(hr))
188 {
189 Log(hSession, L"Failed to create app domain. Error code 0x%X", hr);
190 return false;
191 }
192
193 return true;
194}
195
196/// <summary>
197/// Locates a specific method in a specific class and assembly.
198/// </summary>
199/// <param name="hSession">Handle to the installer session,
200/// used just for logging</param>
201/// <param name="pAppDomain">Application domain in which to
202/// load assemblies.</param>
203/// <param name="szAssembly">Display name of the assembly
204/// containing the method.</param>
205/// <param name="szClass">Fully-qualified name of the class
206/// containing the method.</param>
207/// <param name="szMethod">Name of the method.</param>
208/// <param name="ppMethod">Returned method interface.</param>
209/// <returns>True if the method was located, otherwise false.</returns>
210/// <remarks>Only public static methods are searched. Method
211/// parameter types are not considered; if there are multiple
212/// matching methods with different parameters, an error results.</remarks>
213bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
214 const wchar_t* szAssembly, const wchar_t* szClass,
215 const wchar_t* szMethod, _MethodInfo** ppMethod)
216{
217 HRESULT hr;
218 _Assembly* pAssembly = NULL;
219 BSTR bstrAssemblyName = SysAllocString(szAssembly);
220 if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY;
221 else
222 {
223 hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
224 SysFreeString(bstrAssemblyName);
225 }
226 if (FAILED(hr))
227 {
228 Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr);
229 return false;
230 }
231
232 _Type* pType = NULL;
233 BSTR bstrClass = SysAllocString(szClass);
234 if (bstrClass == NULL) hr = E_OUTOFMEMORY;
235 else
236 {
237 hr = pAssembly->GetType_2(bstrClass, &pType);
238 SysFreeString(bstrClass);
239 }
240 pAssembly->Release();
241 if (FAILED(hr) || pType == NULL)
242 {
243 Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr);
244 return false;
245 }
246
247 BSTR bstrMethod = SysAllocString(szMethod);
248 if (bstrMethod == NULL) hr = E_OUTOFMEMORY;
249 else
250 {
251 hr = pType->GetMethod_2(bstrMethod,
252 (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod);
253 SysFreeString(bstrMethod);
254 }
255 pType->Release();
256 if (FAILED(hr) || *ppMethod == NULL)
257 {
258 Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr);
259 return false;
260 }
261 return true;
262}
diff --git a/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp
new file mode 100644
index 00000000..a49cdeec
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EmbeddedUI.cpp
@@ -0,0 +1,281 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6// Globals for keeping track of things across UI messages.
7static const wchar_t* g_szWorkingDir;
8static ICorRuntimeHost* g_pClrHost;
9static _AppDomain* g_pAppDomain;
10static _MethodInfo* g_pProcessMessageMethod;
11static _MethodInfo* g_pShutdownMethod;
12
13// Reserve extra space for strings to be replaced at build time.
14#define NULLSPACE \
15L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
16L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
17L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
18L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
19
20// Prototypes for local functions.
21// See the function definitions for comments.
22
23bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession,
24 const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult);
25
26/// <summary>
27/// First entry-point for the UI DLL when loaded and called by MSI.
28/// Extracts the payload, hosts the CLR, and invokes the managed
29/// initialize method.
30/// </summary>
31/// <param name="hSession">Handle to the installer session,
32/// used for logging errors and to be passed on to the managed initialize method.</param>
33/// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table
34/// have been extracted, and where additional payload from this package will be extracted.</param>
35/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
36/// the managed initialize method.</param>
37extern "C"
38UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel)
39{
40 // If the managed initialize method cannot be called, continue the installation in BASIC UI mode.
41 UINT uiResult = INSTALLUILEVEL_BASIC;
42
43 const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE;
44
45 g_szWorkingDir = szResourcePath;
46
47 wchar_t szModule[MAX_PATH];
48 DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1);
49 if (cchCopied == 0)
50 {
51 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
52 return uiResult;
53 }
54 else if (cchCopied == MAX_PATH - 1)
55 {
56 Log(hSession, L"Failed to get module path -- path is too long.");
57 return uiResult;
58 }
59
60 Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir);
61 int err = ExtractCabinet(szModule, g_szWorkingDir);
62 if (err != 0)
63 {
64 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
65 Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as "
66 L"any file contained in the embedded UI package.");
67 return uiResult;
68 }
69
70 wchar_t szConfigFilePath[MAX_PATH + 20];
71 StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir);
72 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config");
73
74 const wchar_t* szConfigFile = szConfigFilePath;
75 if (!PathFileExists(szConfigFilePath))
76 {
77 szConfigFile = NULL;
78 }
79
80 wchar_t szWIAssembly[MAX_PATH + 50];
81 StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir);
82 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
83
84 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost))
85 {
86 if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir,
87 szConfigFile, &g_pAppDomain))
88 {
89 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
90 const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy";
91 const wchar_t* szInitMethod = L"Initialize";
92 const wchar_t* szProcessMessageMethod = L"ProcessMessage";
93 const wchar_t* szShutdownMethod = L"Shutdown";
94
95 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
96 szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) &&
97 GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
98 szProxyClass, szShutdownMethod, &g_pShutdownMethod))
99 {
100 _MethodInfo* pInitMethod;
101 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
102 szProxyClass, szInitMethod, &pInitMethod))
103 {
104 bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult);
105 pInitMethod->Release();
106 if (invokeSuccess)
107 {
108 if (uiResult == 0)
109 {
110 return ERROR_SUCCESS;
111 }
112 else if (uiResult == ERROR_INSTALL_USEREXIT)
113 {
114 // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT.
115 // So return success here and then IDCANCEL on the next progress message.
116 uiResult = 0;
117 *pdwInternalUILevel = INSTALLUILEVEL_NONE;
118 Log(hSession, L"Initialization canceled by user.");
119 }
120 }
121 }
122 }
123
124 g_pProcessMessageMethod->Release();
125 g_pProcessMessageMethod = NULL;
126 g_pShutdownMethod->Release();
127 g_pShutdownMethod = NULL;
128
129 g_pClrHost->UnloadDomain(g_pAppDomain);
130 g_pAppDomain->Release();
131 g_pAppDomain = NULL;
132 }
133 g_pClrHost->Stop();
134 g_pClrHost->Release();
135 g_pClrHost = NULL;
136 }
137
138 return uiResult;
139}
140
141/// <summary>
142/// Entry-point for UI progress messages received from the MSI engine during an active installation.
143/// Forwards the progress messages to the managed handler method and returns its result.
144/// </summary>
145extern "C"
146INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord)
147{
148 if (g_pProcessMessageMethod == NULL)
149 {
150 // Initialization was canceled.
151 return IDCANCEL;
152 }
153
154 VARIANT vResult;
155 VariantInit(&vResult);
156
157 VARIANT vNull;
158 vNull.vt = VT_EMPTY;
159
160 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2);
161 VARIANT vMessageType;
162 vMessageType.vt = VT_I4;
163 vMessageType.lVal = (LONG) uiMessageType;
164 LONG index = 0;
165 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType);
166 if (FAILED(hr)) goto LExit;
167 VARIANT vRecord;
168 vRecord.vt = VT_I4;
169 vRecord.lVal = (LONG) hRecord;
170 index = 1;
171 hr = SafeArrayPutElement(saArgs, &index, &vRecord);
172 if (FAILED(hr)) goto LExit;
173
174 hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult);
175
176LExit:
177 SafeArrayDestroy(saArgs);
178 if (SUCCEEDED(hr))
179 {
180 return vResult.intVal;
181 }
182 else
183 {
184 return -1;
185 }
186}
187
188/// <summary>
189/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed.
190/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR.
191/// </summary>
192extern "C"
193DWORD __stdcall ShutdownEmbeddedUI()
194{
195 if (g_pShutdownMethod != NULL)
196 {
197 VARIANT vNull;
198 vNull.vt = VT_EMPTY;
199 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
200 g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL);
201 SafeArrayDestroy(saArgs);
202
203 g_pClrHost->UnloadDomain(g_pAppDomain);
204 g_pAppDomain->Release();
205 g_pClrHost->Stop();
206 g_pClrHost->Release();
207 }
208
209 return 0;
210}
211
212/// <summary>
213/// Loads and invokes the managed portion of the proxy.
214/// </summary>
215/// <param name="pInitMethod">Managed initialize method to be invoked.</param>
216/// <param name="hSession">Handle to the installer session,
217/// used for logging errors and to be passed on to the managed initialize method.</param>
218/// <param name="szClassName">Name of the UI class to be loaded.
219/// This must be of the form: AssemblyName!Namespace.Class</param>
220/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
221/// the managed initialize method.</param>
222/// <param name="puiResult">Return value of the invoked initialize method.</param>
223/// <returns>True if the managed proxy was invoked successfully, or an
224/// error code if there was some error. Note the initialize method itself may
225/// return an error via puiResult while this method still returns true
226/// since the invocation was successful.</returns>
227bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult)
228{
229 VARIANT vResult;
230 VariantInit(&vResult);
231
232 VARIANT vNull;
233 vNull.vt = VT_EMPTY;
234
235 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
236 VARIANT vSessionHandle;
237 vSessionHandle.vt = VT_I4;
238 vSessionHandle.lVal = (LONG) hSession;
239 LONG index = 0;
240 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
241 if (FAILED(hr)) goto LExit;
242 VARIANT vEntryPoint;
243 vEntryPoint.vt = VT_BSTR;
244 vEntryPoint.bstrVal = SysAllocString(szClassName);
245 if (vEntryPoint.bstrVal == NULL)
246 {
247 hr = E_OUTOFMEMORY;
248 goto LExit;
249 }
250 index = 1;
251 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
252 if (FAILED(hr)) goto LExit;
253 VARIANT vUILevel;
254 vUILevel.vt = VT_I4;
255 vUILevel.ulVal = *pdwInternalUILevel;
256 index = 2;
257 hr = SafeArrayPutElement(saArgs, &index, &vUILevel);
258 if (FAILED(hr)) goto LExit;
259
260 hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult);
261
262LExit:
263 SafeArrayDestroy(saArgs);
264 if (SUCCEEDED(hr))
265 {
266 *puiResult = (UINT) vResult.lVal;
267 if ((*puiResult & 0xFFFF) == 0)
268 {
269 // Due to interop limitations, the successful resulting UILevel is returned
270 // as the high-word of the return value instead of via a ref parameter.
271 *pdwInternalUILevel = *puiResult >> 16;
272 *puiResult = 0;
273 }
274 return true;
275 }
276 else
277 {
278 Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr);
279 return false;
280 }
281}
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.def b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def
new file mode 100644
index 00000000..dd28b920
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.def
@@ -0,0 +1,140 @@
1; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3
4LIBRARY "SfxCA"
5
6EXPORTS
7
8CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000
9CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001
10CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002
11CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003
12CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004
13CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005
14CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006
15CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007
16CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008
17CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009
18CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010
19CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011
20CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012
21CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013
22CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014
23CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015
24CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016
25CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017
26CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018
27CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019
28CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020
29CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021
30CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022
31CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023
32CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024
33CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025
34CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026
35CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027
36CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028
37CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029
38CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030
39CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031
40CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032
41CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033
42CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034
43CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035
44CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036
45CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037
46CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038
47CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039
48CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040
49CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041
50CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042
51CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043
52CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044
53CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045
54CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046
55CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047
56CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048
57CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049
58CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050
59CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051
60CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052
61CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053
62CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054
63CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055
64CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056
65CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057
66CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058
67CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059
68CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060
69CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061
70CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062
71CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063
72CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064
73CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065
74CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066
75CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067
76CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068
77CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069
78CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070
79CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071
80CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072
81CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073
82CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074
83CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075
84CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076
85CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077
86CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078
87CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079
88CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080
89CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081
90CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082
91CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083
92CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084
93CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085
94CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086
95CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087
96CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088
97CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089
98CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090
99CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091
100CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092
101CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093
102CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094
103CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095
104CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096
105CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097
106CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098
107CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099
108CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100
109CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101
110CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102
111CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103
112CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104
113CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105
114CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106
115CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107
116CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108
117CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109
118CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110
119CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111
120CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112
121CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113
122CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114
123CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115
124CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116
125CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117
126CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118
127CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119
128CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120
129CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121
130CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122
131CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123
132CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124
133CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125
134CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126
135CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127
136
137zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc
138zzzInitializeEmbeddedUI=InitializeEmbeddedUI
139zzzEmbeddedUIHandler=EmbeddedUIHandler
140zzzShutdownEmbeddedUI=ShutdownEmbeddedUI
diff --git a/src/samples/Dtf/Tools/SfxCA/EntryPoints.h b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h
new file mode 100644
index 00000000..bd2fa970
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/EntryPoints.h
@@ -0,0 +1,162 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3int InvokeCustomAction(MSIHANDLE hSession,
4 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint);
5
6/// <summary>
7/// Macro for defining and exporting a custom action entrypoint.
8/// </summary>
9/// <param name="name">Name of the entrypoint as exported from
10/// the DLL.</param>
11/// <param name="method">Path to the managed custom action method,
12/// in the form: "AssemblyName!Namespace.Class.Method"</param>
13/// <remarks>
14/// To prevent the exported name from being decorated, add
15/// /EXPORT:name to the linker options for every entrypoint.
16/// </remarks>
17#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \
18 name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); }
19
20// TEMPLATE ENTRYPOINTS
21// To be edited by the MakeSfxCA tool.
22
23#define NULLSPACE \
24L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
25L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
26L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
27L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
28
29#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \
30 CustomActionEntryPoint##id##, \
31 L"CustomActionEntryPoint" sid NULLSPACE)
32
33TEMPLATE_CA_ENTRYPOINT(000,L"000");
34TEMPLATE_CA_ENTRYPOINT(001,L"001");
35TEMPLATE_CA_ENTRYPOINT(002,L"002");
36TEMPLATE_CA_ENTRYPOINT(003,L"003");
37TEMPLATE_CA_ENTRYPOINT(004,L"004");
38TEMPLATE_CA_ENTRYPOINT(005,L"005");
39TEMPLATE_CA_ENTRYPOINT(006,L"006");
40TEMPLATE_CA_ENTRYPOINT(007,L"007");
41TEMPLATE_CA_ENTRYPOINT(008,L"008");
42TEMPLATE_CA_ENTRYPOINT(009,L"009");
43TEMPLATE_CA_ENTRYPOINT(010,L"010");
44TEMPLATE_CA_ENTRYPOINT(011,L"011");
45TEMPLATE_CA_ENTRYPOINT(012,L"012");
46TEMPLATE_CA_ENTRYPOINT(013,L"013");
47TEMPLATE_CA_ENTRYPOINT(014,L"014");
48TEMPLATE_CA_ENTRYPOINT(015,L"015");
49TEMPLATE_CA_ENTRYPOINT(016,L"016");
50TEMPLATE_CA_ENTRYPOINT(017,L"017");
51TEMPLATE_CA_ENTRYPOINT(018,L"018");
52TEMPLATE_CA_ENTRYPOINT(019,L"019");
53TEMPLATE_CA_ENTRYPOINT(020,L"020");
54TEMPLATE_CA_ENTRYPOINT(021,L"021");
55TEMPLATE_CA_ENTRYPOINT(022,L"022");
56TEMPLATE_CA_ENTRYPOINT(023,L"023");
57TEMPLATE_CA_ENTRYPOINT(024,L"024");
58TEMPLATE_CA_ENTRYPOINT(025,L"025");
59TEMPLATE_CA_ENTRYPOINT(026,L"026");
60TEMPLATE_CA_ENTRYPOINT(027,L"027");
61TEMPLATE_CA_ENTRYPOINT(028,L"028");
62TEMPLATE_CA_ENTRYPOINT(029,L"029");
63TEMPLATE_CA_ENTRYPOINT(030,L"030");
64TEMPLATE_CA_ENTRYPOINT(031,L"031");
65TEMPLATE_CA_ENTRYPOINT(032,L"032");
66TEMPLATE_CA_ENTRYPOINT(033,L"033");
67TEMPLATE_CA_ENTRYPOINT(034,L"034");
68TEMPLATE_CA_ENTRYPOINT(035,L"035");
69TEMPLATE_CA_ENTRYPOINT(036,L"036");
70TEMPLATE_CA_ENTRYPOINT(037,L"037");
71TEMPLATE_CA_ENTRYPOINT(038,L"038");
72TEMPLATE_CA_ENTRYPOINT(039,L"039");
73TEMPLATE_CA_ENTRYPOINT(040,L"040");
74TEMPLATE_CA_ENTRYPOINT(041,L"041");
75TEMPLATE_CA_ENTRYPOINT(042,L"042");
76TEMPLATE_CA_ENTRYPOINT(043,L"043");
77TEMPLATE_CA_ENTRYPOINT(044,L"044");
78TEMPLATE_CA_ENTRYPOINT(045,L"045");
79TEMPLATE_CA_ENTRYPOINT(046,L"046");
80TEMPLATE_CA_ENTRYPOINT(047,L"047");
81TEMPLATE_CA_ENTRYPOINT(048,L"048");
82TEMPLATE_CA_ENTRYPOINT(049,L"049");
83TEMPLATE_CA_ENTRYPOINT(050,L"050");
84TEMPLATE_CA_ENTRYPOINT(051,L"051");
85TEMPLATE_CA_ENTRYPOINT(052,L"052");
86TEMPLATE_CA_ENTRYPOINT(053,L"053");
87TEMPLATE_CA_ENTRYPOINT(054,L"054");
88TEMPLATE_CA_ENTRYPOINT(055,L"055");
89TEMPLATE_CA_ENTRYPOINT(056,L"056");
90TEMPLATE_CA_ENTRYPOINT(057,L"057");
91TEMPLATE_CA_ENTRYPOINT(058,L"058");
92TEMPLATE_CA_ENTRYPOINT(059,L"059");
93TEMPLATE_CA_ENTRYPOINT(060,L"060");
94TEMPLATE_CA_ENTRYPOINT(061,L"061");
95TEMPLATE_CA_ENTRYPOINT(062,L"062");
96TEMPLATE_CA_ENTRYPOINT(063,L"063");
97TEMPLATE_CA_ENTRYPOINT(064,L"064");
98TEMPLATE_CA_ENTRYPOINT(065,L"065");
99TEMPLATE_CA_ENTRYPOINT(066,L"066");
100TEMPLATE_CA_ENTRYPOINT(067,L"067");
101TEMPLATE_CA_ENTRYPOINT(068,L"068");
102TEMPLATE_CA_ENTRYPOINT(069,L"069");
103TEMPLATE_CA_ENTRYPOINT(070,L"070");
104TEMPLATE_CA_ENTRYPOINT(071,L"071");
105TEMPLATE_CA_ENTRYPOINT(072,L"072");
106TEMPLATE_CA_ENTRYPOINT(073,L"073");
107TEMPLATE_CA_ENTRYPOINT(074,L"074");
108TEMPLATE_CA_ENTRYPOINT(075,L"075");
109TEMPLATE_CA_ENTRYPOINT(076,L"076");
110TEMPLATE_CA_ENTRYPOINT(077,L"077");
111TEMPLATE_CA_ENTRYPOINT(078,L"078");
112TEMPLATE_CA_ENTRYPOINT(079,L"079");
113TEMPLATE_CA_ENTRYPOINT(080,L"080");
114TEMPLATE_CA_ENTRYPOINT(081,L"081");
115TEMPLATE_CA_ENTRYPOINT(082,L"082");
116TEMPLATE_CA_ENTRYPOINT(083,L"083");
117TEMPLATE_CA_ENTRYPOINT(084,L"084");
118TEMPLATE_CA_ENTRYPOINT(085,L"085");
119TEMPLATE_CA_ENTRYPOINT(086,L"086");
120TEMPLATE_CA_ENTRYPOINT(087,L"087");
121TEMPLATE_CA_ENTRYPOINT(088,L"088");
122TEMPLATE_CA_ENTRYPOINT(089,L"089");
123TEMPLATE_CA_ENTRYPOINT(090,L"090");
124TEMPLATE_CA_ENTRYPOINT(091,L"091");
125TEMPLATE_CA_ENTRYPOINT(092,L"092");
126TEMPLATE_CA_ENTRYPOINT(093,L"093");
127TEMPLATE_CA_ENTRYPOINT(094,L"094");
128TEMPLATE_CA_ENTRYPOINT(095,L"095");
129TEMPLATE_CA_ENTRYPOINT(096,L"096");
130TEMPLATE_CA_ENTRYPOINT(097,L"097");
131TEMPLATE_CA_ENTRYPOINT(098,L"098");
132TEMPLATE_CA_ENTRYPOINT(099,L"099");
133TEMPLATE_CA_ENTRYPOINT(100,L"100");
134TEMPLATE_CA_ENTRYPOINT(101,L"101");
135TEMPLATE_CA_ENTRYPOINT(102,L"102");
136TEMPLATE_CA_ENTRYPOINT(103,L"103");
137TEMPLATE_CA_ENTRYPOINT(104,L"104");
138TEMPLATE_CA_ENTRYPOINT(105,L"105");
139TEMPLATE_CA_ENTRYPOINT(106,L"106");
140TEMPLATE_CA_ENTRYPOINT(107,L"107");
141TEMPLATE_CA_ENTRYPOINT(108,L"108");
142TEMPLATE_CA_ENTRYPOINT(109,L"109");
143TEMPLATE_CA_ENTRYPOINT(110,L"110");
144TEMPLATE_CA_ENTRYPOINT(111,L"111");
145TEMPLATE_CA_ENTRYPOINT(112,L"112");
146TEMPLATE_CA_ENTRYPOINT(113,L"113");
147TEMPLATE_CA_ENTRYPOINT(114,L"114");
148TEMPLATE_CA_ENTRYPOINT(115,L"115");
149TEMPLATE_CA_ENTRYPOINT(116,L"116");
150TEMPLATE_CA_ENTRYPOINT(117,L"117");
151TEMPLATE_CA_ENTRYPOINT(118,L"118");
152TEMPLATE_CA_ENTRYPOINT(119,L"119");
153TEMPLATE_CA_ENTRYPOINT(120,L"120");
154TEMPLATE_CA_ENTRYPOINT(121,L"121");
155TEMPLATE_CA_ENTRYPOINT(122,L"122");
156TEMPLATE_CA_ENTRYPOINT(123,L"123");
157TEMPLATE_CA_ENTRYPOINT(124,L"124");
158TEMPLATE_CA_ENTRYPOINT(125,L"125");
159TEMPLATE_CA_ENTRYPOINT(126,L"126");
160TEMPLATE_CA_ENTRYPOINT(127,L"127");
161
162// Note: Keep in sync with EntryPoints.def
diff --git a/src/samples/Dtf/Tools/SfxCA/Extract.cpp b/src/samples/Dtf/Tools/SfxCA/Extract.cpp
new file mode 100644
index 00000000..171cf52f
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/Extract.cpp
@@ -0,0 +1,282 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5//---------------------------------------------------------------------
6// CABINET EXTRACTION
7//---------------------------------------------------------------------
8
9// Globals make this code unsuited for multhreaded use,
10// but FDI doesn't provide any other way to pass context.
11
12// Handle to the FDI (cab extraction) engine. Need access to this in a callback.
13static HFDI g_hfdi;
14
15// FDI is not unicode-aware, so avoid passing these paths through the callbacks.
16static const wchar_t* g_szExtractDir;
17static const wchar_t* g_szCabFile;
18
19// Offset into the source file where the cabinet really starts.
20// Used to trick FDI into extracting from a concatenated cabinet.
21static int g_lCabOffset;
22
23// Use the secure CRT version of _wsopen if available.
24#ifdef __GOT_SECURE_LIB__
25#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
26#else
27#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
28#endif
29
30/// <summary>
31/// FDI callback to open a cabinet file.
32/// </summary>
33/// <param name="pszFile">Name of the file to be opened. This parameter
34/// is ignored since with our limited use this method is only ever called
35/// to open the main cabinet file.</param>
36/// <param name="oflag">Type of operations allowed.</param>
37/// <param name="pmode">Permission setting.</param>
38/// <returns>Integer file handle, or -1 if the file could not be opened.</returns>
39/// <remarks>
40/// To support reading from a cabinet that is concatenated onto
41/// another file, this function first searches for the offset of the cabinet,
42/// then saves that offset for use in recalculating later seeks.
43/// </remarks>
44static FNOPEN(CabOpen)
45{
46 UNREFERENCED_PARAMETER(pszFile);
47 int hf;
48 _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
49 if (hf != -1)
50 {
51 FDICABINETINFO cabInfo;
52 int length = _lseek(hf, 0, SEEK_END);
53 for(int offset = 0; offset < length; offset += 256)
54 {
55 if (_lseek(hf, offset, SEEK_SET) != offset) break;
56 if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
57 {
58 g_lCabOffset = offset;
59 _lseek(hf, offset, SEEK_SET);
60 return hf;
61 }
62 }
63 _close(hf);
64 }
65 return -1;
66}
67
68/// <summary>
69/// FDI callback to seek within a file.
70/// </summary>
71/// <param name="hf">File handle.</param>
72/// <param name="dist">Seek distance</param>
73/// <param name="seektype">Whether to seek relative to the
74/// beginning, current position, or end of the file.</param>
75/// <returns>Resultant position within the cabinet.</returns>
76/// <remarks>
77/// To support reading from a cabinet that is concatenated onto
78/// another file, this function recalculates seeks based on the
79/// offset that was determined when the cabinet was opened.
80/// </remarks>
81static FNSEEK(CabSeek)
82{
83 if (seektype == SEEK_SET) dist += g_lCabOffset;
84 int pos = _lseek((int) hf, dist, seektype);
85 pos -= g_lCabOffset;
86 return pos;
87}
88
89/// <summary>
90/// Ensures a directory and its parent directory path exists.
91/// </summary>
92/// <param name="szDirPath">Directory path, not including file name.</param>
93/// <returns>0 if the directory exists or was successfully created, else nonzero.</returns>
94/// <remarks>
95/// This function modifies characters in szDirPath, but always restores them
96/// regardless of error condition.
97/// </remarks>
98static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
99{
100 int ret = 0;
101 if (!::CreateDirectoryW(szDirPath, NULL))
102 {
103 UINT err = ::GetLastError();
104 if (err != ERROR_ALREADY_EXISTS)
105 {
106 // Directory creation failed for some reason other than already existing.
107 // Try to create the parent directory first.
108 wchar_t* szLastSlash = NULL;
109 for (wchar_t* sz = szDirPath; *sz; sz++)
110 {
111 if (*sz == L'\\')
112 {
113 szLastSlash = sz;
114 }
115 }
116 if (szLastSlash)
117 {
118 // Temporarily take one directory off the path and recurse.
119 *szLastSlash = L'\0';
120 ret = EnsureDirectoryExists(szDirPath);
121 *szLastSlash = L'\\';
122
123 // Try to create the directory if all parents are created.
124 if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
125 {
126 err = ::GetLastError();
127 if (err != ERROR_ALREADY_EXISTS)
128 {
129 ret = -1;
130 }
131 }
132 }
133 else
134 {
135 ret = -1;
136 }
137 }
138 }
139 return ret;
140}
141
142/// <summary>
143/// Ensures a file's directory and its parent directory path exists.
144/// </summary>
145/// <param name="szDirPath">Path including file name.</param>
146/// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns>
147/// <remarks>
148/// This function modifies characters in szFilePath, but always restores them
149/// regardless of error condition.
150/// </remarks>
151static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
152{
153 int ret = 0;
154 wchar_t* szLastSlash = NULL;
155 for (wchar_t* sz = szFilePath; *sz; sz++)
156 {
157 if (*sz == L'\\')
158 {
159 szLastSlash = sz;
160 }
161 }
162 if (szLastSlash)
163 {
164 *szLastSlash = L'\0';
165 ret = EnsureDirectoryExists(szFilePath);
166 *szLastSlash = L'\\';
167 }
168 return ret;
169}
170
171/// <summary>
172/// FDI callback for handling files in the cabinet.
173/// </summary>
174/// <param name="fdint">Type of notification.</param>
175/// <param name="pfdin">Structure containing data about the notification.</param>
176/// <remarks>
177/// Refer to fdi.h for more comments on this notification callback.
178/// </remarks>
179static FNFDINOTIFY(CabNotification)
180{
181 // fdintCOPY_FILE:
182 // Called for each file that *starts* in the current cabinet, giving
183 // the client the opportunity to request that the file be copied or
184 // skipped.
185 // Entry:
186 // pfdin->psz1 = file name in cabinet
187 // pfdin->cb = uncompressed size of file
188 // pfdin->date = file date
189 // pfdin->time = file time
190 // pfdin->attribs = file attributes
191 // pfdin->iFolder = file's folder index
192 // Exit-Success:
193 // Return non-zero file handle for destination file; FDI writes
194 // data to this file use the PFNWRITE function supplied to FDICreate,
195 // and then calls fdintCLOSE_FILE_INFO to close the file and set
196 // the date, time, and attributes.
197 // Exit-Failure:
198 // Returns 0 => Skip file, do not copy
199 // Returns -1 => Abort FDICopy() call
200 if (fdint == fdintCOPY_FILE)
201 {
202 size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
203 size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
204 wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
205 if (szFilePath == NULL) return -1;
206 StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
207 StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
208 MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
209 szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
210 int hf = -1;
211 if (EnsureFileDirectoryExists(szFilePath) == 0)
212 {
213 _wsopen__s(hf, szFilePath,
214 _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
215 _SH_DENYWR, _S_IREAD | _S_IWRITE);
216 }
217 return hf;
218 }
219
220 // fdintCLOSE_FILE_INFO:
221 // Called after all of the data has been written to a target file.
222 // This function must close the file and set the file date, time,
223 // and attributes.
224 // Entry:
225 // pfdin->psz1 = file name in cabinet
226 // pfdin->hf = file handle
227 // pfdin->date = file date
228 // pfdin->time = file time
229 // pfdin->attribs = file attributes
230 // pfdin->iFolder = file's folder index
231 // pfdin->cb = Run After Extract (0 - don't run, 1 Run)
232 // Exit-Success:
233 // Returns TRUE
234 // Exit-Failure:
235 // Returns FALSE, or -1 to abort
236 else if (fdint == fdintCLOSE_FILE_INFO)
237 {
238 _close((int) pfdin->hf);
239 return TRUE;
240 }
241 return 0;
242}
243
244/// <summary>
245/// Extracts all contents of a cabinet file to a directory.
246/// </summary>
247/// <param name="szCabFile">Path to the cabinet file to be extracted.
248/// The cabinet may actually start at some offset within the file,
249/// as long as that offset is a multiple of 256.</param>
250/// <param name="szExtractDir">Directory where files are to be extracted.
251/// This directory must already exist, but should be empty.</param>
252/// <returns>0 if the cabinet was extracted successfully,
253/// or an error code if any error occurred.</returns>
254/// <remarks>
255/// The extraction will not overwrite any files in the destination
256/// directory; extraction will be interrupted and fail if any files
257/// with the same name already exist.
258/// </remarks>
259int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
260{
261 ERF erf;
262 // Most of the FDI callbacks can be handled by existing CRT I/O functions.
263 // For our functionality we only need to handle the open and seek callbacks.
264 HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
265 (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
266 CabSeek, cpu80386, &erf);
267 if (hfdi != NULL)
268 {
269 g_hfdi = hfdi;
270 g_szCabFile = szCabFile;
271 g_szExtractDir = szExtractDir;
272 char szEmpty[1] = {0};
273 if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
274 {
275 FDIDestroy(hfdi);
276 return 0;
277 }
278 FDIDestroy(hfdi);
279 }
280
281 return erf.erfOper;
282}
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp
new file mode 100644
index 00000000..ba59fdf7
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsi.cpp
@@ -0,0 +1,629 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "RemoteMsiSession.h"
5
6
7//
8// Ensures that the request buffer is large enough to hold a request,
9// reallocating the buffer if necessary.
10// It will also reduce the buffer size if the previous allocation was very large.
11//
12static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired)
13{
14 // It will also reduce the buffer size if the previous allocation was very large.
15 if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf))
16 {
17 if (*pszBuf != NULL)
18 {
19 SecureZeroMemory(*pszBuf, *pcchBuf);
20 delete[] *pszBuf;
21 }
22
23 *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired);
24 *pszBuf = new wchar_t[*pcchBuf];
25
26 if (*pszBuf == NULL)
27 {
28 return ERROR_OUTOFMEMORY;
29 }
30 }
31
32 return ERROR_SUCCESS;
33}
34
35typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1);
36typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1);
37typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1);
38typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1);
39typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1);
40typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2);
41typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
42typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
43typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
44typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
45typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3);
46
47UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
48{
49 int in1 = pReq->fields[0].iValue;
50 int out1;
51 UINT ret = (UINT) func(in1, &out1);
52 if (ret == 0)
53 {
54 pResp->fields[1].vt = VT_I4;
55 pResp->fields[1].iValue = out1;
56 }
57 return ret;
58}
59
60UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
61{
62 int in1 = pReq->fields[0].iValue;
63 int in2 = pReq->fields[1].iValue;
64 int out1;
65 UINT ret = (UINT) func(in1, in2, &out1);
66 if (ret == 0)
67 {
68 pResp->fields[1].vt = VT_I4;
69 pResp->fields[1].iValue = out1;
70 }
71 return ret;
72}
73
74UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
75{
76 int in1 = pReq->fields[0].iValue;
77 wchar_t* in2 = pReq->fields[1].szValue;
78 int out1;
79 UINT ret = (UINT) func(in1, in2, &out1);
80 if (ret == 0)
81 {
82 pResp->fields[1].vt = VT_I4;
83 pResp->fields[1].iValue = out1;
84 }
85 return ret;
86}
87
88UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
89{
90 int in1 = pReq->fields[0].iValue;
91 wchar_t* in2 = pReq->fields[1].szValue;
92 int in3 = pReq->fields[2].iValue;
93 int out1;
94 UINT ret = (UINT) func(in1, in2, in3, &out1);
95 if (ret == 0)
96 {
97 pResp->fields[1].vt = VT_I4;
98 pResp->fields[1].iValue = out1;
99 }
100 return ret;
101}
102
103UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
104{
105 int in1 = pReq->fields[0].iValue;
106 wchar_t* in2 = pReq->fields[1].szValue;
107 int in3 = pReq->fields[2].iValue;
108 int in4 = pReq->fields[3].iValue;
109 int out1;
110 UINT ret = (UINT) func(in1, in2, in3, in4, &out1);
111 if (ret == 0)
112 {
113 pResp->fields[1].vt = VT_I4;
114 pResp->fields[1].iValue = out1;
115 }
116 return ret;
117}
118
119UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
120{
121 int in1 = pReq->fields[0].iValue;
122 wchar_t* in2 = pReq->fields[1].szValue;
123 int out1, out2;
124 UINT ret = (UINT) func(in1, in2, &out1, &out2);
125 if (ret == 0)
126 {
127 pResp->fields[1].vt = VT_I4;
128 pResp->fields[1].iValue = out1;
129 pResp->fields[2].vt = VT_I4;
130 pResp->fields[2].iValue = out2;
131 }
132 return ret;
133}
134
135UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
136{
137 int in1 = pReq->fields[0].iValue;
138 szBuf[0] = L'\0';
139 DWORD cchValue = cchBuf;
140 UINT ret = (UINT) func(in1, szBuf, &cchValue);
141 if (ret == ERROR_MORE_DATA)
142 {
143 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
144 if (ret == 0)
145 {
146 ret = (UINT) func(in1, szBuf, &cchValue);
147 }
148 }
149 if (ret == 0)
150 {
151 pResp->fields[1].vt = VT_LPWSTR;
152 pResp->fields[1].szValue = szBuf;
153 }
154 return ret;
155}
156
157MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
158{
159 int in1 = pReq->fields[0].iValue;
160 szBuf[0] = L'\0';
161 DWORD cchValue = cchBuf;
162 MSIDBERROR ret = func(in1, szBuf, &cchValue);
163 if (ret == MSIDBERROR_MOREDATA)
164 {
165 if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue))
166 {
167 ret = func(in1, szBuf, &cchValue);
168 }
169 }
170 if (ret != MSIDBERROR_MOREDATA)
171 {
172 pResp->fields[1].vt = VT_LPWSTR;
173 pResp->fields[1].szValue = szBuf;
174 }
175 return ret;
176}
177
178UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
179{
180 int in1 = pReq->fields[0].iValue;
181 int in2 = pReq->fields[1].iValue;
182 szBuf[0] = L'\0';
183 DWORD cchValue = cchBuf;
184 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
185 if (ret == ERROR_MORE_DATA)
186 {
187 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
188 if (ret == 0)
189 {
190 ret = (UINT) func(in1, in2, szBuf, &cchValue);
191 }
192 }
193 if (ret == 0)
194 {
195 pResp->fields[1].vt = VT_LPWSTR;
196 pResp->fields[1].szValue = szBuf;
197 }
198 return ret;
199}
200
201UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
202{
203 int in1 = pReq->fields[0].iValue;
204 wchar_t* in2 = pReq->fields[1].szValue;
205 szBuf[0] = L'\0';
206 DWORD cchValue = cchBuf;
207 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
208 if (ret == ERROR_MORE_DATA)
209 {
210 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
211 if (ret == 0)
212 {
213 ret = (UINT) func(in1, in2, szBuf, &cchValue);
214 }
215 }
216 if (ret == 0)
217 {
218 pResp->fields[1].vt = VT_LPWSTR;
219 pResp->fields[1].szValue = szBuf;
220 }
221 return ret;
222}
223
224UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
225{
226 int in1 = pReq->fields[0].iValue;
227 wchar_t* in2 = pReq->fields[1].szValue;
228 int in3 = pReq->fields[2].iValue;
229 int in4 = pReq->fields[3].iValue;
230 szBuf[0] = L'\0';
231 DWORD cchValue = cchBuf;
232 int out2, out3;
233 UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
234 if (ret == ERROR_MORE_DATA)
235 {
236 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
237 if (ret == 0)
238 {
239 ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
240 }
241 }
242 if (ret == 0)
243 {
244 pResp->fields[1].vt = VT_LPWSTR;
245 pResp->fields[1].szValue = szBuf;
246 pResp->fields[2].vt = VT_I4;
247 pResp->fields[2].iValue = out2;
248 pResp->fields[3].vt = VT_I4;
249 pResp->fields[3].iValue = out3;
250 }
251 return ret;
252}
253
254void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp)
255{
256 SecureZeroMemory(pResp, sizeof(RequestData));
257
258 UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024);
259
260 if (0 == ret)
261 {
262 switch (id)
263 {
264 case RemoteMsiSession::EndSession:
265 {
266 this->ExitCode = pReq->fields[0].iValue;
267 }
268 break;
269 case RemoteMsiSession::MsiCloseHandle:
270 {
271 MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue;
272 ret = ::MsiCloseHandle(h);
273 }
274 break;
275 case RemoteMsiSession::MsiProcessMessage:
276 {
277 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
278 INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue;
279 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
280 ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord);
281 }
282 break;
283 case RemoteMsiSession::MsiGetProperty:
284 {
285 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend);
286 }
287 break;
288 case RemoteMsiSession::MsiSetProperty:
289 {
290 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
291 const wchar_t* szName = pReq->fields[1].szValue;
292 const wchar_t* szValue = pReq->fields[2].szValue;
293 ret = ::MsiSetProperty(hInstall, szName, szValue);
294 }
295 break;
296 case RemoteMsiSession::MsiCreateRecord:
297 {
298 UINT cParams = pReq->fields[0].uiValue;
299 ret = ::MsiCreateRecord(cParams);
300 }
301 break;
302 case RemoteMsiSession::MsiRecordGetFieldCount:
303 {
304 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
305 ret = ::MsiRecordGetFieldCount(hRecord);
306 }
307 break;
308 case RemoteMsiSession::MsiRecordGetInteger:
309 {
310 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
311 UINT iField = pReq->fields[1].uiValue;
312 ret = ::MsiRecordGetInteger(hRecord, iField);
313 }
314 break;
315 case RemoteMsiSession::MsiRecordSetInteger:
316 {
317 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
318 UINT iField = pReq->fields[1].uiValue;
319 int iValue = pReq->fields[2].iValue;
320 ret = ::MsiRecordSetInteger(hRecord, iField, iValue);
321 }
322 break;
323 case RemoteMsiSession::MsiRecordGetString:
324 {
325 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend);
326 }
327 break;
328 case RemoteMsiSession::MsiRecordSetString:
329 {
330 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
331 UINT iField = pReq->fields[1].uiValue;
332 const wchar_t* szValue = pReq->fields[2].szValue;
333 ret = ::MsiRecordSetString(hRecord, iField, szValue);
334 }
335 break;
336 case RemoteMsiSession::MsiRecordClearData:
337 {
338 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
339 ret = ::MsiRecordClearData(hRecord);
340 }
341 break;
342 case RemoteMsiSession::MsiRecordIsNull:
343 {
344 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
345 UINT iField = pReq->fields[1].uiValue;
346 ret = ::MsiRecordIsNull(hRecord, iField);
347 }
348 break;
349 case RemoteMsiSession::MsiFormatRecord:
350 {
351 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend);
352 }
353 break;
354 case RemoteMsiSession::MsiGetActiveDatabase:
355 {
356 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
357 ret = (UINT) ::MsiGetActiveDatabase(hInstall);
358 }
359 break;
360 case RemoteMsiSession::MsiDatabaseOpenView:
361 {
362 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp);
363 }
364 break;
365 case RemoteMsiSession::MsiViewExecute:
366 {
367 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
368 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue;
369 ret = ::MsiViewExecute(hView, hRecord);
370 }
371 break;
372 case RemoteMsiSession::MsiViewFetch:
373 {
374 ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp);
375 }
376 break;
377 case RemoteMsiSession::MsiViewModify:
378 {
379 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
380 MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue;
381 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
382 ret = ::MsiViewModify(hView, eModifyMode, hRecord);
383 }
384 break;
385 case RemoteMsiSession::MsiViewGetError:
386 {
387 ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend);
388 }
389 break;
390 case RemoteMsiSession::MsiViewGetColumnInfo:
391 {
392 ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp);
393 }
394 break;
395 case RemoteMsiSession::MsiDatabaseGetPrimaryKeys:
396 {
397 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp);
398 }
399 break;
400 case RemoteMsiSession::MsiDatabaseIsTablePersistent:
401 {
402 MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue;
403 const wchar_t* szTable = pReq->fields[1].szValue;
404 ret = ::MsiDatabaseIsTablePersistent(hDb, szTable);
405 }
406 break;
407 case RemoteMsiSession::MsiDoAction:
408 {
409 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
410 const wchar_t* szAction = pReq->fields[1].szValue;
411 ret = ::MsiDoAction(hInstall, szAction);
412 }
413 break;
414 case RemoteMsiSession::MsiEnumComponentCosts:
415 {
416 ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend);
417 }
418 break;
419 case RemoteMsiSession::MsiEvaluateCondition:
420 {
421 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
422 const wchar_t* szCondition = pReq->fields[1].szValue;
423 ret = ::MsiEvaluateCondition(hInstall, szCondition);
424 }
425 break;
426 case RemoteMsiSession::MsiGetComponentState:
427 {
428 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp);
429 }
430 break;
431 case RemoteMsiSession::MsiGetFeatureCost:
432 {
433 ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp);
434 }
435 break;
436 case RemoteMsiSession::MsiGetFeatureState:
437 {
438 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp);
439 }
440 break;
441 case RemoteMsiSession::MsiGetFeatureValidStates:
442 {
443 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp);
444 }
445 break;
446 case RemoteMsiSession::MsiGetLanguage:
447 {
448 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
449 ret = ::MsiGetLanguage(hInstall);
450 }
451 break;
452 case RemoteMsiSession::MsiGetLastErrorRecord:
453 {
454 ret = ::MsiGetLastErrorRecord();
455 }
456 break;
457 case RemoteMsiSession::MsiGetMode:
458 {
459 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
460 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue;
461 ret = ::MsiGetMode(hInstall, iRunMode);
462 }
463 break;
464 case RemoteMsiSession::MsiGetSourcePath:
465 {
466 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend);
467 }
468 break;
469 case RemoteMsiSession::MsiGetSummaryInformation:
470 {
471 ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp);
472 }
473 break;
474 case RemoteMsiSession::MsiGetTargetPath:
475 {
476 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend);
477 }
478 break;
479 case RemoteMsiSession::MsiRecordDataSize:
480 {
481 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
482 UINT iField = pReq->fields[1].uiValue;
483 ret = ::MsiRecordDataSize(hRecord, iField);
484 }
485 break;
486 case RemoteMsiSession::MsiRecordReadStream:
487 {
488 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
489 UINT iField = pReq->fields[1].uiValue;
490 DWORD cbRead = (DWORD) pReq->fields[2].uiValue;
491 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2);
492 if (ret == 0)
493 {
494 ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead);
495 if (ret == 0)
496 {
497 pResp->fields[1].vt = VT_STREAM;
498 pResp->fields[1].szValue = m_pBufSend;
499 pResp->fields[2].vt = VT_I4;
500 pResp->fields[2].uiValue = (UINT) cbRead;
501 }
502 }
503 }
504 break;
505 case RemoteMsiSession::MsiRecordSetStream:
506 {
507 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
508 UINT iField = pReq->fields[1].uiValue;
509 const wchar_t* szFilePath = pReq->fields[2].szValue;
510 ret = ::MsiRecordSetStream(hRecord, iField, szFilePath);
511 }
512 break;
513 case RemoteMsiSession::MsiSequence:
514 {
515 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
516 const wchar_t* szTable = pReq->fields[1].szValue;
517 UINT iSequenceMode = pReq->fields[2].uiValue;
518 ret = ::MsiSequence(hRecord, szTable, iSequenceMode);
519 }
520 break;
521 case RemoteMsiSession::MsiSetComponentState:
522 {
523 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
524 const wchar_t* szComponent = pReq->fields[1].szValue;
525 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
526 ret = ::MsiSetComponentState(hInstall, szComponent, iState);
527 }
528 break;
529 case RemoteMsiSession::MsiSetFeatureAttributes:
530 {
531 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
532 const wchar_t* szFeature = pReq->fields[1].szValue;
533 DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue;
534 ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs);
535 }
536 break;
537 case RemoteMsiSession::MsiSetFeatureState:
538 {
539 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
540 const wchar_t* szFeature = pReq->fields[1].szValue;
541 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
542 ret = ::MsiSetFeatureState(hInstall, szFeature, iState);
543 }
544 break;
545 case RemoteMsiSession::MsiSetInstallLevel:
546 {
547 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
548 int iInstallLevel = pReq->fields[1].iValue;
549 ret = ::MsiSetInstallLevel(hInstall, iInstallLevel);
550 }
551 break;
552 case RemoteMsiSession::MsiSetMode:
553 {
554 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
555 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue;
556 BOOL fState = (BOOL) pReq->fields[2].iValue;
557 ret = ::MsiSetMode(hInstall, iRunMode, fState);
558 }
559 break;
560 case RemoteMsiSession::MsiSetTargetPath:
561 {
562 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
563 const wchar_t* szFolder = pReq->fields[1].szValue;
564 const wchar_t* szFolderPath = pReq->fields[2].szValue;
565 ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath);
566 }
567 break;
568 case RemoteMsiSession::MsiSummaryInfoGetProperty:
569 {
570 MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue;
571 UINT uiProperty = pReq->fields[1].uiValue;
572 UINT uiDataType;
573 int iValue;
574 FILETIME ftValue;
575 m_pBufSend[0] = L'\0';
576 DWORD cchValue = m_cbBufSend;
577 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
578 if (ret == ERROR_MORE_DATA)
579 {
580 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue);
581 if (ret == 0)
582 {
583 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
584 }
585 }
586 if (ret == 0)
587 {
588 pResp->fields[1].vt = VT_UI4;
589 pResp->fields[1].uiValue = uiDataType;
590
591 switch (uiDataType)
592 {
593 case VT_I2:
594 case VT_I4:
595 pResp->fields[2].vt = VT_I4;
596 pResp->fields[2].iValue = iValue;
597 break;
598 case VT_FILETIME:
599 pResp->fields[2].vt = VT_UI4;
600 pResp->fields[2].iValue = ftValue.dwHighDateTime;
601 pResp->fields[3].vt = VT_UI4;
602 pResp->fields[3].iValue = ftValue.dwLowDateTime;
603 break;
604 case VT_LPSTR:
605 pResp->fields[2].vt = VT_LPWSTR;
606 pResp->fields[2].szValue = m_pBufSend;
607 break;
608 }
609 }
610 }
611 break;
612 case RemoteMsiSession::MsiVerifyDiskSpace:
613 {
614 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
615 ret = ::MsiVerifyDiskSpace(hInstall);
616 }
617 break;
618
619 default:
620 {
621 ret = ERROR_INVALID_FUNCTION;
622 }
623 break;
624 }
625 }
626
627 pResp->fields[0].vt = VT_UI4;
628 pResp->fields[0].uiValue = ret;
629}
diff --git a/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h
new file mode 100644
index 00000000..90c7c01f
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/RemoteMsiSession.h
@@ -0,0 +1,898 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define LARGE_BUFFER_THRESHOLD 65536 // bytes
4#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts
5
6///////////////////////////////////////////////////////////////////////////////////////
7// RemoteMsiSession //
8//////////////////////
9//
10// Allows accessing MSI APIs from another process using named pipes.
11//
12class RemoteMsiSession
13{
14public:
15
16 // This enumeration MUST stay in sync with the
17 // managed equivalent in RemotableNativeMethods.cs!
18 enum RequestId
19 {
20 EndSession = 0,
21 MsiCloseHandle,
22 MsiCreateRecord,
23 MsiDatabaseGetPrimaryKeys,
24 MsiDatabaseIsTablePersistent,
25 MsiDatabaseOpenView,
26 MsiDoAction,
27 MsiEnumComponentCosts,
28 MsiEvaluateCondition,
29 MsiFormatRecord,
30 MsiGetActiveDatabase,
31 MsiGetComponentState,
32 MsiGetFeatureCost,
33 MsiGetFeatureState,
34 MsiGetFeatureValidStates,
35 MsiGetLanguage,
36 MsiGetLastErrorRecord,
37 MsiGetMode,
38 MsiGetProperty,
39 MsiGetSourcePath,
40 MsiGetSummaryInformation,
41 MsiGetTargetPath,
42 MsiProcessMessage,
43 MsiRecordClearData,
44 MsiRecordDataSize,
45 MsiRecordGetFieldCount,
46 MsiRecordGetInteger,
47 MsiRecordGetString,
48 MsiRecordIsNull,
49 MsiRecordReadStream,
50 MsiRecordSetInteger,
51 MsiRecordSetStream,
52 MsiRecordSetString,
53 MsiSequence,
54 MsiSetComponentState,
55 MsiSetFeatureAttributes,
56 MsiSetFeatureState,
57 MsiSetInstallLevel,
58 MsiSetMode,
59 MsiSetProperty,
60 MsiSetTargetPath,
61 MsiSummaryInfoGetProperty,
62 MsiVerifyDiskSpace,
63 MsiViewExecute,
64 MsiViewFetch,
65 MsiViewGetError,
66 MsiViewGetColumnInfo,
67 MsiViewModify,
68 };
69
70 static const int MAX_REQUEST_FIELDS = 4;
71
72 // Used to pass data back and forth for remote API calls,
73 // including in & out params & return values.
74 // Only strings and ints are supported.
75 struct RequestData
76 {
77 struct
78 {
79 VARENUM vt;
80 union {
81 int iValue;
82 UINT uiValue;
83 DWORD cchValue;
84 LPWSTR szValue;
85 BYTE* sValue;
86 DWORD cbValue;
87 };
88 } fields[MAX_REQUEST_FIELDS];
89 };
90
91public:
92
93 // This value is set from the single data parameter in the EndSession request.
94 // It saves the exit code of the out-of-proc custom action.
95 int ExitCode;
96
97 /////////////////////////////////////////////////////////////////////////////////////
98 // RemoteMsiSession constructor
99 //
100 // Creates a new remote session instance, for use either by the server
101 // or client process.
102 //
103 // szName - Identifies the session instance being remoted. The server and
104 // the client must use the same name. The name should be unique
105 // enough to avoid conflicting with other instances on the system.
106 //
107 // fServer - True if the calling process is the server process, false if the
108 // calling process is the client process.
109 //
110 RemoteMsiSession(const wchar_t* szName, bool fServer=true)
111 : m_fServer(fServer),
112 m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"),
113 m_szPipeName(NULL),
114 m_hPipe(NULL),
115 m_fConnecting(false),
116 m_fConnected(false),
117 m_hReceiveThread(NULL),
118 m_hReceiveStopEvent(NULL),
119 m_pBufReceive(NULL),
120 m_cbBufReceive(0),
121 m_pBufSend(NULL),
122 m_cbBufSend(0),
123 ExitCode(ERROR_INSTALL_FAILURE)
124 {
125 SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED));
126 m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
127 }
128
129 /////////////////////////////////////////////////////////////////////////////////////
130 // RemoteMsiSession destructor
131 //
132 // Closes any open handles and frees any allocated memory.
133 //
134 ~RemoteMsiSession()
135 {
136 WaitExitCode();
137 if (m_hPipe != NULL)
138 {
139 CloseHandle(m_hPipe);
140 m_hPipe = NULL;
141 }
142 if (m_overlapped.hEvent != NULL)
143 {
144 CloseHandle(m_overlapped.hEvent);
145 m_overlapped.hEvent = NULL;
146 }
147 if (m_szPipeName != NULL)
148 {
149 delete[] m_szPipeName;
150 m_szPipeName = NULL;
151 }
152 if (m_pBufReceive != NULL)
153 {
154 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
155 delete[] m_pBufReceive;
156 m_pBufReceive = NULL;
157 }
158 if (m_pBufSend != NULL)
159 {
160 SecureZeroMemory(m_pBufSend, m_cbBufSend);
161 delete[] m_pBufSend;
162 m_pBufSend = NULL;
163 }
164 m_fConnecting = false;
165 m_fConnected = false;
166 }
167
168 /////////////////////////////////////////////////////////////////////////////////////
169 // RemoteMsiSession::WaitExitCode()
170 //
171 // Waits for the server processing thread to complete.
172 //
173 void WaitExitCode()
174 {
175 if (m_hReceiveThread != NULL)
176 {
177 SetEvent(m_hReceiveStopEvent);
178 WaitForSingleObject(m_hReceiveThread, INFINITE);
179 CloseHandle(m_hReceiveThread);
180 m_hReceiveThread = NULL;
181 }
182 }
183
184 /////////////////////////////////////////////////////////////////////////////////////
185 // RemoteMsiSession::Connect()
186 //
187 // Connects the inter-process communication channel.
188 // (Currently implemented as a named pipe.)
189 //
190 // This method must be called first by the server process, then by the client
191 // process. The method does not block; the server will asynchronously wait
192 // for the client process to make the connection.
193 //
194 // Returns: 0 on success, Win32 error code on failure.
195 //
196 virtual DWORD Connect()
197 {
198 const wchar_t* szPipePrefix = L"\\\\.\\pipe\\";
199 size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1;
200 m_szPipeName = new wchar_t[cchPipeNameBuf];
201
202 if (m_szPipeName == NULL)
203 {
204 return ERROR_OUTOFMEMORY;
205 }
206 else
207 {
208 wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix);
209 wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName);
210
211 if (m_fServer)
212 {
213 return this->ConnectPipeServer();
214 }
215 else
216 {
217 return this->ConnectPipeClient();
218 }
219 }
220 }
221
222 /////////////////////////////////////////////////////////////////////////////////////
223 // RemoteMsiSession::IsConnected()
224 //
225 // Checks if the server process and client process are currently connected.
226 //
227 virtual bool IsConnected() const
228 {
229 return m_fConnected;
230 }
231
232 /////////////////////////////////////////////////////////////////////////////////////
233 // RemoteMsiSession::ProcessRequests()
234 //
235 // For use by the service process. Watches for requests in the input buffer and calls
236 // the callback for each one.
237 //
238 // This method does not block; it spawns a separate thread to do the work.
239 //
240 // Returns: 0 on success, Win32 error code on failure.
241 //
242 virtual DWORD ProcessRequests()
243 {
244 return this->StartProcessingReqests();
245 }
246
247 /////////////////////////////////////////////////////////////////////////////////////
248 // RemoteMsiSession::SendRequest()
249 //
250 // For use by the client process. Sends a request to the server and
251 // synchronously waits on a response, up to the timeout value.
252 //
253 // id - ID code of the MSI API call being requested.
254 //
255 // pRequest - Pointer to a data structure containing request parameters.
256 //
257 // ppResponse - [OUT] Pointer to a location that receives the response parameters.
258 //
259 // Returns: 0 on success, Win32 error code on failure.
260 // Returns WAIT_TIMEOUT if no response was received in time.
261 //
262 virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse)
263 {
264 if (m_fServer)
265 {
266 return ERROR_INVALID_OPERATION;
267 }
268
269 if (!m_fConnected)
270 {
271 *ppResponse = NULL;
272 return 0;
273 }
274
275 DWORD dwRet = this->SendRequest(id, pRequest);
276 if (dwRet != 0)
277 {
278 return dwRet;
279 }
280
281 if (id != EndSession)
282 {
283 static RequestData response;
284 if (ppResponse != NULL)
285 {
286 *ppResponse = &response;
287 }
288
289 return this->ReceiveResponse(id, &response);
290 }
291 else
292 {
293 CloseHandle(m_hPipe);
294 m_hPipe = NULL;
295 m_fConnected = false;
296 return 0;
297 }
298 }
299
300private:
301
302 //
303 // Do not allow assignment.
304 //
305 RemoteMsiSession& operator=(const RemoteMsiSession&);
306
307 //
308 // Called only by the server process.
309 // Create a new thread to handle receiving requests.
310 //
311 DWORD StartProcessingReqests()
312 {
313 if (!m_fServer || m_hReceiveStopEvent != NULL)
314 {
315 return ERROR_INVALID_OPERATION;
316 }
317
318 DWORD dwRet = 0;
319
320 m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
321
322 if (m_hReceiveStopEvent == NULL)
323 {
324 dwRet = GetLastError();
325 }
326 else
327 {
328 if (m_hReceiveThread != NULL)
329 {
330 CloseHandle(m_hReceiveThread);
331 }
332
333 m_hReceiveThread = CreateThread(NULL, 0,
334 RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL);
335
336 if (m_hReceiveThread == NULL)
337 {
338 dwRet = GetLastError();
339 CloseHandle(m_hReceiveStopEvent);
340 m_hReceiveStopEvent = NULL;
341 }
342 }
343
344 return dwRet;
345 }
346
347 //
348 // Called only by the watcher process.
349 // First verify the connection is complete. Then continually read and parse messages,
350 // invoke the callback, and send the replies.
351 //
352 static DWORD WINAPI ProcessRequestsThreadStatic(void* pv)
353 {
354 return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread();
355 }
356
357 DWORD ProcessRequestsThread()
358 {
359 DWORD dwRet;
360
361 dwRet = CompleteConnection();
362 if (dwRet != 0)
363 {
364 if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0;
365 }
366
367 while (m_fConnected)
368 {
369 RequestId id;
370 RequestData req;
371 dwRet = ReceiveRequest(&id, &req);
372 if (dwRet != 0)
373 {
374 if (dwRet == ERROR_OPERATION_ABORTED ||
375 dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA)
376 {
377 dwRet = 0;
378 }
379 }
380 else
381 {
382 RequestData resp;
383 ProcessRequest(id, &req, &resp);
384
385 if (id == EndSession)
386 {
387 break;
388 }
389
390 dwRet = SendResponse(id, &resp);
391 if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA)
392 {
393 dwRet = 0;
394 }
395 }
396 }
397
398 CloseHandle(m_hReceiveStopEvent);
399 m_hReceiveStopEvent = NULL;
400 return dwRet;
401 }
402
403 //
404 // Called only by the server process's receive thread.
405 // Read one request into a RequestData object.
406 //
407 DWORD ReceiveRequest(RequestId* pId, RequestData* pReq)
408 {
409 DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId));
410
411 if (dwRet == 0)
412 {
413 dwRet = this->ReadRequestData(pReq);
414 }
415
416 return dwRet;
417 }
418
419 //
420 // Called by the server process's receive thread or the client's request call
421 // to read the response. Read data from the pipe, allowing interruption by the
422 // stop event if on the server.
423 //
424 DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead)
425 {
426 DWORD dwRet = 0;
427 DWORD dwTotalBytesRead = 0;
428
429 while (dwRet == 0 && dwTotalBytesRead < cbRead)
430 {
431 DWORD dwBytesReadThisTime;
432 ResetEvent(m_overlapped.hEvent);
433 if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped))
434 {
435 dwRet = GetLastError();
436 if (dwRet == ERROR_IO_PENDING)
437 {
438 if (m_fServer)
439 {
440 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
441 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
442 }
443 else
444 {
445 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
446 }
447
448 if (dwRet == WAIT_OBJECT_0)
449 {
450 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE))
451 {
452 dwRet = GetLastError();
453 }
454 }
455 else if (dwRet == WAIT_FAILED)
456 {
457 dwRet = GetLastError();
458 }
459 else
460 {
461 dwRet = ERROR_OPERATION_ABORTED;
462 }
463 }
464 }
465
466 dwTotalBytesRead += dwBytesReadThisTime;
467 }
468
469 if (dwRet != 0)
470 {
471 if (m_fServer)
472 {
473 CancelIo(m_hPipe);
474 DisconnectNamedPipe(m_hPipe);
475 }
476 else
477 {
478 CloseHandle(m_hPipe);
479 m_hPipe = NULL;
480 }
481 m_fConnected = false;
482 }
483
484 return dwRet;
485 }
486
487 //
488 // Called only by the server process.
489 // Given a request, invoke the MSI API and return the response.
490 // This is implemented in RemoteMsi.cpp.
491 //
492 void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp);
493
494 //
495 // Called only by the client process.
496 // Send request data over the pipe.
497 //
498 DWORD SendRequest(RequestId id, const RequestData* pRequest)
499 {
500 DWORD dwRet = WriteRequestData(id, pRequest);
501
502 if (dwRet != 0)
503 {
504 m_fConnected = false;
505 CloseHandle(m_hPipe);
506 m_hPipe = NULL;
507 }
508
509 return dwRet;
510 }
511
512 //
513 // Called only by the server process.
514 // Just send a response over the pipe.
515 //
516 DWORD SendResponse(RequestId id, const RequestData* pResp)
517 {
518 DWORD dwRet = WriteRequestData(id, pResp);
519
520 if (dwRet != 0)
521 {
522 DisconnectNamedPipe(m_hPipe);
523 m_fConnected = false;
524 }
525
526 return dwRet;
527 }
528
529 //
530 // Called either by the client or server process.
531 // Writes data to the pipe for a request or response.
532 //
533 DWORD WriteRequestData(RequestId id, const RequestData* pReq)
534 {
535 DWORD dwRet = 0;
536
537 RequestData req = *pReq; // Make a copy because the const data can't be changed.
538
539 dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId));
540 if (dwRet != 0)
541 {
542 return dwRet;
543 }
544
545 BYTE* sValues[MAX_REQUEST_FIELDS] = {0};
546 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
547 {
548 if (req.fields[i].vt == VT_LPWSTR)
549 {
550 sValues[i] = (BYTE*) req.fields[i].szValue;
551 req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue);
552 }
553 else if (req.fields[i].vt == VT_STREAM)
554 {
555 sValues[i] = req.fields[i].sValue;
556 req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue;
557 }
558 }
559
560 dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData));
561 if (dwRet != 0)
562 {
563 return dwRet;
564 }
565
566 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
567 {
568 if (sValues[i] != NULL)
569 {
570 DWORD cbValue;
571 if (req.fields[i].vt == VT_LPWSTR)
572 {
573 cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR);
574 }
575 else
576 {
577 cbValue = req.fields[i].cbValue;
578 }
579
580 dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue);
581 if (dwRet != 0)
582 {
583 break;
584 }
585 }
586 }
587
588 return dwRet;
589 }
590
591 //
592 // Called when writing a request or response. Writes data to
593 // the pipe, allowing interruption by the stop event if on the server.
594 //
595 DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite)
596 {
597 DWORD dwRet = 0;
598 DWORD dwTotalBytesWritten = 0;
599
600 while (dwRet == 0 && dwTotalBytesWritten < cbWrite)
601 {
602 DWORD dwBytesWrittenThisTime;
603 ResetEvent(m_overlapped.hEvent);
604 if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped))
605 {
606 dwRet = GetLastError();
607 if (dwRet == ERROR_IO_PENDING)
608 {
609 if (m_fServer)
610 {
611 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
612 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
613 }
614 else
615 {
616 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
617 }
618
619 if (dwRet == WAIT_OBJECT_0)
620 {
621 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE))
622 {
623 dwRet = GetLastError();
624 }
625 }
626 else if (dwRet == WAIT_FAILED)
627 {
628 dwRet = GetLastError();
629 }
630 else
631 {
632 dwRet = ERROR_OPERATION_ABORTED;
633 }
634 }
635 }
636
637 dwTotalBytesWritten += dwBytesWrittenThisTime;
638 }
639
640 return dwRet;
641 }
642
643 //
644 // Called either by the client or server process.
645 // Reads data from the pipe for a request or response.
646 //
647 DWORD ReadRequestData(RequestData* pReq)
648 {
649 DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData));
650
651 if (dwRet == 0)
652 {
653 DWORD cbData = 0;
654 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
655 {
656 if (pReq->fields[i].vt == VT_LPWSTR)
657 {
658 cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
659 }
660 else if (pReq->fields[i].vt == VT_STREAM)
661 {
662 cbData += pReq->fields[i].cbValue;
663 }
664 }
665
666 if (cbData > 0)
667 {
668 if (!CheckRequestDataBuf(cbData))
669 {
670 return ERROR_OUTOFMEMORY;
671 }
672
673 dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData);
674 if (dwRet == 0)
675 {
676 DWORD dwOffset = 0;
677 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
678 {
679 if (pReq->fields[i].vt == VT_LPWSTR)
680 {
681 LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset);
682 dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
683 pReq->fields[i].szValue = szTemp;
684 }
685 else if (pReq->fields[i].vt == VT_STREAM)
686 {
687 BYTE* sTemp = m_pBufReceive + dwOffset;
688 dwOffset += pReq->fields[i].cbValue;
689 pReq->fields[i].sValue = sTemp;
690 }
691 }
692 }
693 }
694 }
695
696 return dwRet;
697 }
698
699 //
700 // Called only by the client process.
701 // Wait for a response on the pipe. If no response is received before the timeout,
702 // then give up and close the connection.
703 //
704 DWORD ReceiveResponse(RequestId id, RequestData* pResp)
705 {
706 RequestId responseId;
707 DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId));
708 if (dwRet == 0 && responseId != id)
709 {
710 dwRet = ERROR_OPERATION_ABORTED;
711 }
712
713 if (dwRet == 0)
714 {
715 dwRet = this->ReadRequestData(pResp);
716 }
717
718 return dwRet;
719 }
720
721 //
722 // Called only by the server process's receive thread.
723 // Try to complete and verify an asynchronous connection operation.
724 //
725 DWORD CompleteConnection()
726 {
727 DWORD dwRet = 0;
728 if (m_fConnecting)
729 {
730 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
731 DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
732
733 if (dwWaitRes == WAIT_OBJECT_0)
734 {
735 m_fConnecting = false;
736
737 DWORD dwUnused;
738 if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE))
739 {
740 m_fConnected = true;
741 }
742 else
743 {
744 dwRet = GetLastError();
745 }
746 }
747 else if (dwWaitRes == WAIT_FAILED)
748 {
749 CancelIo(m_hPipe);
750 dwRet = GetLastError();
751 }
752 else
753 {
754 CancelIo(m_hPipe);
755 dwRet = ERROR_OPERATION_ABORTED;
756 }
757 }
758 return dwRet;
759 }
760
761 //
762 // Called only by the server process.
763 // Creates a named pipe instance and begins asynchronously waiting
764 // for a connection from the client process.
765 //
766 DWORD ConnectPipeServer()
767 {
768 DWORD dwRet = 0;
769 const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes
770 m_hPipe = CreateNamedPipe(
771 m_szPipeName,
772 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
773 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
774 1, BUFSIZE, BUFSIZE, 0, NULL);
775 if (m_hPipe == INVALID_HANDLE_VALUE)
776 {
777 m_hPipe = NULL;
778 dwRet = GetLastError();
779 }
780 else if (ConnectNamedPipe(m_hPipe, &m_overlapped))
781 {
782 m_fConnected = true;
783 }
784 else
785 {
786 dwRet = GetLastError();
787
788 if (dwRet == ERROR_PIPE_BUSY)
789 {
790 // All pipe instances are busy, so wait for a maximum of 20 seconds
791 dwRet = 0;
792 if (WaitNamedPipe(m_szPipeName, 20000))
793 {
794 m_fConnected = true;
795 }
796 else
797 {
798 dwRet = GetLastError();
799 }
800 }
801
802 if (dwRet == ERROR_IO_PENDING)
803 {
804 dwRet = 0;
805 m_fConnecting = true;
806 }
807 }
808 return dwRet;
809 }
810
811 //
812 // Called only by the client process.
813 // Attemps to open a connection to an existing named pipe instance
814 // which should have already been created by the server process.
815 //
816 DWORD ConnectPipeClient()
817 {
818 DWORD dwRet = 0;
819 m_hPipe = CreateFile(
820 m_szPipeName, GENERIC_READ | GENERIC_WRITE,
821 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
822 if (m_hPipe != INVALID_HANDLE_VALUE)
823 {
824 m_fConnected = true;
825 }
826 else
827 {
828 m_hPipe = NULL;
829 dwRet = GetLastError();
830 }
831 return dwRet;
832 }
833
834 //
835 // Ensures that the request buffer is large enough to hold a request,
836 // reallocating the buffer if necessary.
837 // It will also reduce the buffer size if the previous allocation was very large.
838 //
839 BOOL CheckRequestDataBuf(DWORD cbBuf)
840 {
841 if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive))
842 {
843 if (m_pBufReceive != NULL)
844 {
845 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
846 delete[] m_pBufReceive;
847 }
848 m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf);
849 m_pBufReceive = new BYTE[m_cbBufReceive];
850 if (m_pBufReceive == NULL)
851 {
852 m_cbBufReceive = 0;
853 }
854 }
855 return m_pBufReceive != NULL;
856 }
857
858private:
859
860 // Name of this instance.
861 const wchar_t* m_szName;
862
863 // "\\.\pipe\name"
864 wchar_t* m_szPipeName;
865
866 // Handle to the pipe instance.
867 HANDLE m_hPipe;
868
869 // Handle to the thread that receives requests.
870 HANDLE m_hReceiveThread;
871
872 // Handle to the event used to signal the receive thread to exit.
873 HANDLE m_hReceiveStopEvent;
874
875 // All pipe I/O is done in overlapped mode to avoid unintentional blocking.
876 OVERLAPPED m_overlapped;
877
878 // Dynamically-resized buffer for receiving requests.
879 BYTE* m_pBufReceive;
880
881 // Current size of the receive request buffer.
882 DWORD m_cbBufReceive;
883
884 // Dynamically-resized buffer for sending requests.
885 wchar_t* m_pBufSend;
886
887 // Current size of the send request buffer.
888 DWORD m_cbBufSend;
889
890 // True if this is the server process, false if this is the client process.
891 const bool m_fServer;
892
893 // True if an asynchronous connection operation is currently in progress.
894 bool m_fConnecting;
895
896 // True if the pipe is currently connected.
897 bool m_fConnected;
898};
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp
new file mode 100644
index 00000000..06319f1e
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.cpp
@@ -0,0 +1,363 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "EntryPoints.h"
5#include "SfxUtil.h"
6
7#define MANAGED_CAs_OUT_OF_PROC 1
8
9HMODULE g_hModule;
10bool g_fRunningOutOfProc = false;
11
12RemoteMsiSession* g_pRemote = NULL;
13
14// Prototypes for local functions.
15// See the function definitions for comments.
16
17bool InvokeManagedCustomAction(MSIHANDLE hSession,
18 _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult);
19
20/// <summary>
21/// Entry-point for the CA DLL when re-launched as a separate process;
22/// connects the comm channel for remote MSI APIs, then invokes the
23/// managed custom action entry-point.
24/// </summary>
25/// <remarks>
26/// Do not change the parameters or calling-convention: RUNDLL32
27/// requires this exact signature.
28/// </remarks>
29extern "C"
30void __stdcall InvokeManagedCustomActionOutOfProc(
31 __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow)
32{
33 UNREFERENCED_PARAMETER(hwnd);
34 UNREFERENCED_PARAMETER(hinst);
35 UNREFERENCED_PARAMETER(nCmdShow);
36
37 g_fRunningOutOfProc = true;
38
39 const wchar_t* szSessionName = szCmdLine;
40 MSIHANDLE hSession;
41 const wchar_t* szEntryPoint;
42
43 int i;
44 for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++);
45 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
46 hSession = _wtoi(szCmdLine + i);
47
48 for (; szCmdLine[i] && szCmdLine[i] != L' '; i++);
49 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
50 szEntryPoint = szCmdLine + i;
51
52 g_pRemote = new RemoteMsiSession(szSessionName, false);
53 g_pRemote->Connect();
54
55 int ret = InvokeCustomAction(hSession, NULL, szEntryPoint);
56
57 RemoteMsiSession::RequestData requestData;
58 SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData));
59 requestData.fields[0].vt = VT_I4;
60 requestData.fields[0].iValue = ret;
61 g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL);
62 delete g_pRemote;
63}
64
65/// <summary>
66/// Re-launch this CA DLL as a separate process, and setup a comm channel
67/// for remote MSI API calls back to this process.
68/// </summary>
69int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint)
70{
71 wchar_t szSessionName[100] = {0};
72 swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount());
73
74 RemoteMsiSession remote(szSessionName, true);
75
76 DWORD ret = remote.Connect();
77 if (ret != 0)
78 {
79 Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret);
80 return ERROR_INSTALL_FAILURE;
81 }
82
83 ret = remote.ProcessRequests();
84 if (ret != 0)
85 {
86 Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret);
87 return ERROR_INSTALL_FAILURE;
88 }
89
90 wchar_t szModule[MAX_PATH] = {0};
91 GetModuleFileName(g_hModule, szModule, MAX_PATH);
92
93 const wchar_t* rundll32 = L"rundll32.exe";
94 wchar_t szRunDll32Path[MAX_PATH] = {0};
95 GetSystemDirectory(szRunDll32Path, MAX_PATH);
96 wcscat_s(szRunDll32Path, MAX_PATH, L"\\");
97 wcscat_s(szRunDll32Path, MAX_PATH, rundll32);
98
99 const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc";
100 wchar_t szCommandLine[1024] = {0};
101 swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s",
102 rundll32, szModule, entry, szSessionName, hSession, szEntryPoint);
103
104 STARTUPINFO si;
105 SecureZeroMemory(&si, sizeof(STARTUPINFO));
106 si.cb = sizeof(STARTUPINFO);
107
108 PROCESS_INFORMATION pi;
109 SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
110
111 if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE,
112 0, NULL, NULL, &si, &pi))
113 {
114 DWORD err = GetLastError();
115 Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err);
116 return ERROR_INSTALL_FAILURE;
117 }
118
119 DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE);
120 if (dwWait != WAIT_OBJECT_0)
121 {
122 DWORD err = GetLastError();
123 Log(hSession, L"Failed to wait for CA process. Error code: %d", err);
124 return ERROR_INSTALL_FAILURE;
125 }
126
127 DWORD dwExitCode;
128 BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode);
129 if (!bRet)
130 {
131 DWORD err = GetLastError();
132 Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err);
133 return ERROR_INSTALL_FAILURE;
134 }
135 else if (dwExitCode != 0)
136 {
137 Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode);
138 return ERROR_INSTALL_FAILURE;
139 }
140
141 CloseHandle(pi.hThread);
142 CloseHandle(pi.hProcess);
143
144 remote.WaitExitCode();
145 return remote.ExitCode;
146}
147
148/// <summary>
149/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to
150/// call MSI APIs remotely.
151/// </summary>
152void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse)
153{
154 if (g_fRunningOutOfProc)
155 {
156 g_pRemote->SendRequest(id, pRequest, ppResponse);
157 }
158 else
159 {
160 *ppResponse = NULL;
161 }
162}
163
164/// <summary>
165/// Invokes a managed custom action from native code by
166/// extracting the package to a temporary working directory
167/// then hosting the CLR and locating and calling the entrypoint.
168/// </summary>
169/// <param name="hSession">Handle to the installation session.
170/// Passed to custom action entrypoints by the installer engine.</param>
171/// <param name="szWorkingDir">Directory containing the CA binaries
172/// and the CustomAction.config file defining the entrypoints.
173/// This may be NULL, in which case the current module must have
174/// a concatenated cabinet containing those files, which will be
175/// extracted to a temporary directory.</param>
176/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
177/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
178/// string, or a simple name that maps to a full entrypoint definition
179/// in CustomAction.config.</param>
180/// <returns>The value returned by the managed custom action method,
181/// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns>
182int InvokeCustomAction(MSIHANDLE hSession,
183 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint)
184{
185#ifdef MANAGED_CAs_OUT_OF_PROC
186 if (!g_fRunningOutOfProc && szWorkingDir == NULL)
187 {
188 return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint);
189 }
190#endif
191
192 wchar_t szTempDir[MAX_PATH];
193 bool fDeleteTemp = false;
194 if (szWorkingDir == NULL)
195 {
196 if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH))
197 {
198 return ERROR_INSTALL_FAILURE;
199 }
200 szWorkingDir = szTempDir;
201 fDeleteTemp = true;
202 }
203
204 wchar_t szConfigFilePath[MAX_PATH + 20];
205 StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir);
206 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config");
207
208 const wchar_t* szConfigFile = szConfigFilePath;
209 if (!::PathFileExists(szConfigFilePath))
210 {
211 szConfigFile = NULL;
212 }
213
214 wchar_t szWIAssembly[MAX_PATH + 50];
215 StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir);
216 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
217
218 int iResult = ERROR_INSTALL_FAILURE;
219 ICorRuntimeHost* pHost;
220 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost))
221 {
222 _AppDomain* pAppDomain;
223 if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir,
224 szConfigFile, &pAppDomain))
225 {
226 if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult))
227 {
228 iResult = ERROR_INSTALL_FAILURE;
229 }
230 HRESULT hr = pHost->UnloadDomain(pAppDomain);
231 if (FAILED(hr))
232 {
233 Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr);
234 }
235 pAppDomain->Release();
236 }
237
238 pHost->Stop();
239 pHost->Release();
240 }
241
242 if (fDeleteTemp)
243 {
244 DeleteDirectory(szTempDir);
245 }
246 return iResult;
247}
248
249/// <summary>
250/// Called by the system when the DLL is loaded.
251/// Saves the module handle for later use.
252/// </summary>
253BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved)
254{
255 UNREFERENCED_PARAMETER(pReserved);
256
257 switch (dwReason)
258 {
259 case DLL_PROCESS_ATTACH:
260 g_hModule = hModule;
261 break;
262 case DLL_THREAD_ATTACH:
263 case DLL_THREAD_DETACH:
264 case DLL_PROCESS_DETACH:
265 break;
266 }
267 return TRUE;
268}
269
270/// <summary>
271/// Loads and invokes the managed portion of the proxy.
272/// </summary>
273/// <param name="hSession">Handle to the installer session,
274/// used for logging errors and to be passed on to the custom action.</param>
275/// <param name="pAppDomain">AppDomain which has its application
276/// base set to the CA working directory.</param>
277/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
278/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
279/// string, or a simple name that maps to a full entrypoint definition
280/// in CustomAction.config.</param>
281/// <param name="piResult">Return value of the invoked custom
282/// action method.</param>
283/// <returns>True if the managed proxy was invoked successfully,
284/// false if there was some error. Note the custom action itself may
285/// return an error via piResult while this method still returns true
286/// since the invocation was successful.</returns>
287bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain,
288 const wchar_t* szEntryPoint, int* piResult)
289{
290 VARIANT vResult;
291 ::VariantInit(&vResult);
292
293 const bool f64bit = (sizeof(void*) == sizeof(LONGLONG));
294 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
295 const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy";
296 const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32");
297
298 _MethodInfo* pCAInvokeMethod;
299 if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName,
300 szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod))
301 {
302 return false;
303 }
304
305 HRESULT hr;
306 VARIANT vNull;
307 vNull.vt = VT_EMPTY;
308 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
309 VARIANT vSessionHandle;
310 vSessionHandle.vt = VT_I4;
311 vSessionHandle.intVal = hSession;
312 LONG index = 0;
313 hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
314 if (FAILED(hr)) goto LExit;
315 VARIANT vEntryPoint;
316 vEntryPoint.vt = VT_BSTR;
317 vEntryPoint.bstrVal = SysAllocString(szEntryPoint);
318 if (vEntryPoint.bstrVal == NULL)
319 {
320 hr = E_OUTOFMEMORY;
321 goto LExit;
322 }
323 index = 1;
324 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
325 if (FAILED(hr)) goto LExit;
326 VARIANT vRemotingFunctionPtr;
327#pragma warning(push)
328#pragma warning(disable:4127) // conditional expression is constant
329 if (f64bit)
330#pragma warning(pop)
331 {
332 vRemotingFunctionPtr.vt = VT_I8;
333 vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
334 }
335 else
336 {
337 vRemotingFunctionPtr.vt = VT_I4;
338#pragma warning(push)
339#pragma warning(disable:4302) // truncation
340#pragma warning(disable:4311) // pointer truncation
341 vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
342#pragma warning(pop)
343 }
344 index = 2;
345 hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr);
346 if (FAILED(hr)) goto LExit;
347
348 hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult);
349
350LExit:
351 SafeArrayDestroy(saArgs);
352 pCAInvokeMethod->Release();
353
354 if (FAILED(hr))
355 {
356 Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr);
357 return false;
358 }
359
360 *piResult = vResult.intVal;
361 return true;
362}
363
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.rc b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc
new file mode 100644
index 00000000..4d78194b
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.rc
@@ -0,0 +1,10 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define VER_DLL
4#define VER_LANG_NEUTRAL
5#define VER_ORIGINAL_FILENAME "SfxCA.dll"
6#define VER_INTERNAL_NAME "SfxCA"
7#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action"
8
9// Additional resources here
10
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj
new file mode 100644
index 00000000..aeaaa776
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj
@@ -0,0 +1,68 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 <ItemGroup Label="ProjectConfigurations">
5 <ProjectConfiguration Include="Debug|Win32">
6 <Configuration>Debug</Configuration>
7 <Platform>Win32</Platform>
8 </ProjectConfiguration>
9 <ProjectConfiguration Include="Release|Win32">
10 <Configuration>Release</Configuration>
11 <Platform>Win32</Platform>
12 </ProjectConfiguration>
13 <ProjectConfiguration Include="Debug|x64">
14 <Configuration>Debug</Configuration>
15 <Platform>x64</Platform>
16 </ProjectConfiguration>
17 <ProjectConfiguration Include="Release|x64">
18 <Configuration>Release</Configuration>
19 <Platform>x64</Platform>
20 </ProjectConfiguration>
21 </ItemGroup>
22
23 <PropertyGroup Label="Globals">
24 <ProjectGuid>{55D5BA28-D427-4F53-80C2-FE9EF23C1553}</ProjectGuid>
25 <ConfigurationType>DynamicLibrary</ConfigurationType>
26 <TargetName>SfxCA</TargetName>
27 <PlatformToolset>v142</PlatformToolset>
28 <CharacterSet>Unicode</CharacterSet>
29 <ProjectModuleDefinitionFile>EntryPoints.def</ProjectModuleDefinitionFile>
30 </PropertyGroup>
31 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
32 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
33 <PropertyGroup>
34 <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
35 </PropertyGroup>
36 <ItemGroup>
37 <ClCompile Include="ClrHost.cpp" />
38 <ClCompile Include="Extract.cpp" />
39 <ClCompile Include="precomp.cpp">
40 <PrecompiledHeader>Create</PrecompiledHeader>
41 </ClCompile>
42 <ClCompile Include="RemoteMsi.cpp" />
43 <ClCompile Include="SfxCA.cpp" />
44 <ClCompile Include="SfxUtil.cpp" />
45 <ClCompile Include="EmbeddedUI.cpp" />
46 </ItemGroup>
47 <ItemGroup>
48 <ClInclude Include="precomp.h" />
49 <ClInclude Include="EntryPoints.h" />
50 <ClInclude Include="RemoteMsiSession.h" />
51 <ClInclude Include="SfxUtil.h" />
52 </ItemGroup>
53 <ItemGroup>
54 <None Include="EntryPoints.def" />
55 <None Include="packages.config" />
56 </ItemGroup>
57 <ItemGroup>
58 <ResourceCompile Include="SfxCA.rc" />
59 </ItemGroup>
60 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
61 <Import Project="..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
62 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
63 <PropertyGroup>
64 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
65 </PropertyGroup>
66 <Error Condition="!Exists('..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
67 </Target>
68</Project> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters
new file mode 100644
index 00000000..a5ebf693
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxCA.vcxproj.filters
@@ -0,0 +1,62 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <ClCompile Include="ClrHost.cpp">
5 <Filter>Source Files</Filter>
6 </ClCompile>
7 <ClCompile Include="EmbeddedUI.cpp">
8 <Filter>Source Files</Filter>
9 </ClCompile>
10 <ClCompile Include="Extract.cpp">
11 <Filter>Source Files</Filter>
12 </ClCompile>
13 <ClCompile Include="RemoteMsi.cpp">
14 <Filter>Source Files</Filter>
15 </ClCompile>
16 <ClCompile Include="SfxCA.cpp">
17 <Filter>Source Files</Filter>
18 </ClCompile>
19 <ClCompile Include="SfxUtil.cpp">
20 <Filter>Source Files</Filter>
21 </ClCompile>
22 <ClCompile Include="precomp.cpp">
23 <Filter>Source Files</Filter>
24 </ClCompile>
25 </ItemGroup>
26 <ItemGroup>
27 <ClInclude Include="EntryPoints.h">
28 <Filter>Header Files</Filter>
29 </ClInclude>
30 <ClInclude Include="precomp.h">
31 <Filter>Header Files</Filter>
32 </ClInclude>
33 <ClInclude Include="RemoteMsiSession.h">
34 <Filter>Header Files</Filter>
35 </ClInclude>
36 <ClInclude Include="SfxUtil.h">
37 <Filter>Header Files</Filter>
38 </ClInclude>
39 </ItemGroup>
40 <ItemGroup>
41 <Filter Include="Resource Files">
42 <UniqueIdentifier>{81c92f68-18c2-4cd4-a588-5c3616860dd9}</UniqueIdentifier>
43 </Filter>
44 <Filter Include="Header Files">
45 <UniqueIdentifier>{6cdc30ee-e14d-4679-b92e-3e080535e53b}</UniqueIdentifier>
46 </Filter>
47 <Filter Include="Source Files">
48 <UniqueIdentifier>{1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d}</UniqueIdentifier>
49 </Filter>
50 </ItemGroup>
51 <ItemGroup>
52 <ResourceCompile Include="SfxCA.rc">
53 <Filter>Resource Files</Filter>
54 </ResourceCompile>
55 </ItemGroup>
56 <ItemGroup>
57 <None Include="EntryPoints.def">
58 <Filter>Resource Files</Filter>
59 </None>
60 <None Include="packages.config" />
61 </ItemGroup>
62</Project> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp
new file mode 100644
index 00000000..1bf2c5b2
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.cpp
@@ -0,0 +1,209 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6/// <summary>
7/// Writes a formatted message to the MSI log.
8/// Does out-of-proc MSI calls if necessary.
9/// </summary>
10void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...)
11{
12 const int LOG_BUFSIZE = 4096;
13 wchar_t szBuf[LOG_BUFSIZE];
14 va_list args;
15 va_start(args, szMessage);
16 StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args);
17
18 if (!g_fRunningOutOfProc || NULL == g_pRemote)
19 {
20 MSIHANDLE hRec = MsiCreateRecord(1);
21 MsiRecordSetString(hRec, 0, L"SFXCA: [1]");
22 MsiRecordSetString(hRec, 1, szBuf);
23 MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec);
24 MsiCloseHandle(hRec);
25 }
26 else
27 {
28 // Logging is the only remote-MSI operation done from unmanaged code.
29 // It's not very convenient here because part of the infrastructure
30 // for remote MSI APIs is on the managed side.
31
32 RemoteMsiSession::RequestData req;
33 RemoteMsiSession::RequestData* pResp = NULL;
34 SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData));
35
36 req.fields[0].vt = VT_UI4;
37 req.fields[0].uiValue = 1;
38 g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp);
39 MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue;
40
41 req.fields[0].vt = VT_I4;
42 req.fields[0].iValue = (int) hRec;
43 req.fields[1].vt = VT_UI4;
44 req.fields[1].uiValue = 0;
45 req.fields[2].vt = VT_LPWSTR;
46 req.fields[2].szValue = L"SFXCA: [1]";
47 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
48
49 req.fields[0].vt = VT_I4;
50 req.fields[0].iValue = (int) hRec;
51 req.fields[1].vt = VT_UI4;
52 req.fields[1].uiValue = 1;
53 req.fields[2].vt = VT_LPWSTR;
54 req.fields[2].szValue = szBuf;
55 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
56
57 req.fields[0].vt = VT_I4;
58 req.fields[0].iValue = (int) hSession;
59 req.fields[1].vt = VT_I4;
60 req.fields[1].iValue = (int) INSTALLMESSAGE_INFO;
61 req.fields[2].vt = VT_I4;
62 req.fields[2].iValue = (int) hRec;
63 g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp);
64
65 req.fields[0].vt = VT_I4;
66 req.fields[0].iValue = (int) hRec;
67 req.fields[1].vt = VT_EMPTY;
68 req.fields[2].vt = VT_EMPTY;
69 g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp);
70 }
71}
72
73/// <summary>
74/// Deletes a directory, including all files and subdirectories.
75/// </summary>
76/// <param name="szDir">Path to the directory to delete,
77/// not including a trailing backslash.</param>
78/// <returns>True if the directory was successfully deleted, or false
79/// if the deletion failed (most likely because some files were locked).
80/// </returns>
81bool DeleteDirectory(const wchar_t* szDir)
82{
83 size_t cchDir = wcslen(szDir);
84 size_t cchPathBuf = cchDir + 3 + MAX_PATH;
85 wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t));
86 if (szPath == NULL) return false;
87 StringCchCopy(szPath, cchPathBuf, szDir);
88 StringCchCat(szPath, cchPathBuf, L"\\*");
89 WIN32_FIND_DATA fd;
90 HANDLE hSearch = FindFirstFile(szPath, &fd);
91 while (hSearch != INVALID_HANDLE_VALUE)
92 {
93 StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName);
94 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
95 {
96 if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0)
97 {
98 DeleteDirectory(szPath);
99 }
100 }
101 else
102 {
103 DeleteFile(szPath);
104 }
105 if (!FindNextFile(hSearch, &fd))
106 {
107 FindClose(hSearch);
108 hSearch = INVALID_HANDLE_VALUE;
109 }
110 }
111 return RemoveDirectory(szDir) != 0;
112}
113
114bool DirectoryExists(const wchar_t* szDir)
115{
116 if (szDir != NULL)
117 {
118 DWORD dwAttrs = GetFileAttributes(szDir);
119 if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
120 {
121 return true;
122 }
123 }
124 return false;
125}
126
127/// <summary>
128/// Extracts a cabinet that is concatenated to a module
129/// to a new temporary directory.
130/// </summary>
131/// <param name="hSession">Handle to the installer session,
132/// used just for logging.</param>
133/// <param name="hModule">Module that has the concatenated cabinet.</param>
134/// <param name="szTempDir">Buffer for returning the path of the
135/// created temp directory.</param>
136/// <param name="cchTempDirBuf">Size in characters of the buffer.
137/// <returns>True if the files were extracted, or false if the
138/// buffer was too small or the directory could not be created
139/// or the extraction failed for some other reason.</returns>
140__success(return != false)
141bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
142 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
143{
144 wchar_t szModule[MAX_PATH];
145 DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
146 if (cchCopied == 0)
147 {
148 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
149 return false;
150 }
151 else if (cchCopied == MAX_PATH - 1)
152 {
153 Log(hSession, L"Failed to get module path -- path is too long.");
154 return false;
155 }
156
157 if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
158 {
159 Log(hSession, L"Temp directory buffer is NULL or too small.");
160 return false;
161 }
162 StringCchCopy(szTempDir, cchTempDirBuf, szModule);
163 StringCchCat(szTempDir, cchTempDirBuf, L"-");
164
165 DWORD cchTempDir = (DWORD) wcslen(szTempDir);
166 for (int i = 0; DirectoryExists(szTempDir); i++)
167 {
168 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
169 }
170
171 if (!CreateDirectory(szTempDir, NULL))
172 {
173 cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
174 if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
175 {
176 Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
177 return false;
178 }
179
180 wchar_t* szModuleName = wcsrchr(szModule, L'\\');
181 if (szModuleName == NULL) szModuleName = szModule;
182 else szModuleName = szModuleName + 1;
183 StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
184 StringCchCat(szTempDir, cchTempDirBuf, L"-");
185
186 cchTempDir = (DWORD) wcslen(szTempDir);
187 for (int i = 0; DirectoryExists(szTempDir); i++)
188 {
189 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
190 }
191
192 if (!CreateDirectory(szTempDir, NULL))
193 {
194 Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
195 return false;
196 }
197 }
198
199 Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);
200 int err = ExtractCabinet(szModule, szTempDir);
201 if (err != 0)
202 {
203 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
204 DeleteDirectory(szTempDir);
205 return false;
206 }
207 return true;
208}
209
diff --git a/src/samples/Dtf/Tools/SfxCA/SfxUtil.h b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h
new file mode 100644
index 00000000..af12d8dd
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/SfxUtil.h
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "RemoteMsiSession.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir);
8
9bool DeleteDirectory(const wchar_t* szDir);
10
11__success(return != false)
12bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
13 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf);
14
15bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
16 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost);
17
18bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
19 const wchar_t* szName, const wchar_t* szAppBase,
20 const wchar_t* szConfigFile, _AppDomain** ppAppDomain);
21
22bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
23 const wchar_t* szAssembly, const wchar_t* szClass,
24 const wchar_t* szMethod, _MethodInfo** ppCAMethod);
25
26extern HMODULE g_hModule;
27extern bool g_fRunningOutOfProc;
28
29extern RemoteMsiSession* g_pRemote;
30
31
diff --git a/src/samples/Dtf/Tools/SfxCA/packages.config b/src/samples/Dtf/Tools/SfxCA/packages.config
new file mode 100644
index 00000000..1ffaa8df
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/packages.config
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
4</packages> \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.cpp b/src/samples/Dtf/Tools/SfxCA/precomp.cpp
new file mode 100644
index 00000000..ce82c1d7
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h" \ No newline at end of file
diff --git a/src/samples/Dtf/Tools/SfxCA/precomp.h b/src/samples/Dtf/Tools/SfxCA/precomp.h
new file mode 100644
index 00000000..48d4f011
--- /dev/null
+++ b/src/samples/Dtf/Tools/SfxCA/precomp.h
@@ -0,0 +1,18 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <msiquery.h>
7#include <strsafe.h>
8#include <mscoree.h>
9#include <io.h>
10#include <fcntl.h>
11#include <share.h>
12#include <shlwapi.h>
13#include <sys/stat.h>
14#include <malloc.h>
15#include <fdi.h>
16#include <msiquery.h>
17#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent", "CorReportEvent")
18using namespace mscorlib;
diff --git a/src/samples/Dtf/Tools/Tools.proj b/src/samples/Dtf/Tools/Tools.proj
new file mode 100644
index 00000000..751247dc
--- /dev/null
+++ b/src/samples/Dtf/Tools/Tools.proj
@@ -0,0 +1,15 @@
1<?xml version='1.0' encoding='utf-8'?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <ItemGroup>
7 <ProjectReference Include="MakeSfxCA\MakeSfxCA.csproj" />
8 <ProjectReference Include="SfxCA\SfxCA.vcxproj" />
9 <ProjectReference Include="SfxCA\SfxCA.vcxproj">
10 <Properties>Platform=x64</Properties>
11 </ProjectReference>
12 </ItemGroup>
13
14 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\Traversal.targets" />
15</Project>
diff --git a/src/samples/Dtf/WiFile/WiFile.cs b/src/samples/Dtf/WiFile/WiFile.cs
new file mode 100644
index 00000000..1e5c80df
--- /dev/null
+++ b/src/samples/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/samples/Dtf/WiFile/WiFile.csproj b/src/samples/Dtf/WiFile/WiFile.csproj
new file mode 100644
index 00000000..b5a95481
--- /dev/null
+++ b/src/samples/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.Samples.WiFile</RootNamespace>
10 <AssemblyName>WiFile</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="WiFile.cs" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <Reference Include="System" />
20 <Reference Include="System.Data" />
21 <Reference Include="System.Xml" />
22 <ProjectReference Include="..\..\Libraries\WindowsInstaller.Package\WindowsInstaller.Package.csproj" />
23 <ProjectReference Include="..\..\Libraries\WindowsInstaller\WindowsInstaller.csproj" />
24 </ItemGroup>
25
26 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27</Project>
diff --git a/src/samples/Dtf/XPack/AssemblyInfo.cs b/src/samples/Dtf/XPack/AssemblyInfo.cs
new file mode 100644
index 00000000..6dfb9437
--- /dev/null
+++ b/src/samples/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/samples/Dtf/XPack/XPack.cs b/src/samples/Dtf/XPack/XPack.cs
new file mode 100644
index 00000000..36543a73
--- /dev/null
+++ b/src/samples/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.Samples.XPack
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Text;
9 using WixToolset.Dtf.Compression;
10
11 public class XPack
12 {
13 public static void Usage(TextWriter writer)
14 {
15 writer.WriteLine("Usage: XPack /P <archive.cab> <directory>");
16 writer.WriteLine("Usage: XPack /P <archive.zip> <directory>");
17 writer.WriteLine();
18 writer.WriteLine("Packs all files in a directory tree into an archive,");
19 writer.WriteLine("using either the cab or zip format. Any existing archive");
20 writer.WriteLine("with the same name will be overwritten.");
21 writer.WriteLine();
22 writer.WriteLine("Usage: XPack /U <archive.cab> <directory>");
23 writer.WriteLine("Usage: XPack /U <archive.zip> <directory>");
24 writer.WriteLine();
25 writer.WriteLine("Unpacks all files from a cab or zip archive to the");
26 writer.WriteLine("specified directory. Any existing files with the same");
27 writer.WriteLine("names will be overwritten.");
28 }
29
30 public static void Main(string[] args)
31 {
32 try
33 {
34 if (args.Length == 3 && args[0].ToUpperInvariant() == "/P")
35 {
36 ArchiveInfo a = GetArchive(args[1]);
37 a.Pack(args[2], true, CompressionLevel.Max, ProgressHandler);
38 }
39 else if (args.Length == 3 && args[0].ToUpperInvariant() == "/U")
40 {
41 ArchiveInfo a = GetArchive(args[1]);
42 a.Unpack(args[2], ProgressHandler);
43 }
44 else
45 {
46 Usage(Console.Out);
47 }
48 }
49 catch (Exception ex)
50 {
51 Console.WriteLine(ex);
52 }
53 }
54
55 private static void ProgressHandler(object source, ArchiveProgressEventArgs e)
56 {
57 if (e.ProgressType == ArchiveProgressType.StartFile)
58 {
59 Console.WriteLine(e.CurrentFileName);
60 }
61 }
62
63 private static ArchiveInfo GetArchive(string name)
64 {
65 string extension = Path.GetExtension(name).ToUpperInvariant();
66 if (extension == ".CAB")
67 {
68 return new WixToolset.Dtf.Compression.Cab.CabInfo(name);
69 }
70 else if (extension == ".ZIP")
71 {
72 return new WixToolset.Dtf.Compression.Zip.ZipInfo(name);
73 }
74 else
75 {
76 throw new ArgumentException("Unknown archive file extension: " + extension);
77 }
78 }
79 }
80}
diff --git a/src/samples/Dtf/XPack/XPack.csproj b/src/samples/Dtf/XPack/XPack.csproj
new file mode 100644
index 00000000..778c2d94
--- /dev/null
+++ b/src/samples/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.Samples.XPack</RootNamespace>
10 <AssemblyName>XPack</AssemblyName>
11 <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <Compile Include="AssemblyInfo.cs" />
16 <Compile Include="XPack.cs" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <Reference Include="System" />
21 <ProjectReference Include="..\..\Libraries\Compression.Cab\Compression.Cab.csproj" />
22 <ProjectReference Include="..\..\Libraries\Compression.Zip\Compression.Zip.csproj" />
23 <ProjectReference Include="..\..\Libraries\Compression\Compression.csproj" />
24 </ItemGroup>
25
26 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
27</Project>