diff options
author | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:36:37 -0700 |
commit | 3f583916719eeef598d10a5d4e14ef14f008243b (patch) | |
tree | 3d528e0ddb5c0550954217c97059d2f19cd6152a /src/samples | |
parent | 2e5ab696b8b4666d551b2a0532b95fb7fe6dbe03 (diff) | |
download | wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.gz wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.bz2 wix-3f583916719eeef598d10a5d4e14ef14f008243b.zip |
Merge Dtf
Diffstat (limited to 'src/samples')
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Collections; | ||
6 | using System.Collections.Generic; | ||
7 | using WixToolset.Dtf.Compression.Cab; | ||
8 | |||
9 | namespace WixToolset.Dtf.Samples.DDiff | ||
10 | { | ||
11 | public class CabDiffEngine : IDiffEngine | ||
12 | { | ||
13 | public CabDiffEngine() | ||
14 | { | ||
15 | } | ||
16 | |||
17 | private bool IsCabinetFile(string file) | ||
18 | { | ||
19 | using(FileStream fileStream = File.OpenRead(file)) | ||
20 | { | ||
21 | return new CabEngine().IsArchive(fileStream); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
26 | { | ||
27 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
28 | diffInput2 != null && File.Exists(diffInput2) && | ||
29 | (IsCabinetFile(diffInput1) || IsCabinetFile(diffInput2))) | ||
30 | { | ||
31 | return .80f; | ||
32 | } | ||
33 | else | ||
34 | { | ||
35 | return 0; | ||
36 | } | ||
37 | } | ||
38 | |||
39 | public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
40 | { | ||
41 | bool difference = false; | ||
42 | IComparer caseInsComp = CaseInsensitiveComparer.Default; | ||
43 | |||
44 | // TODO: Make this faster by extracting the whole cab at once. | ||
45 | // TODO: Optimize for the match case by first comparing the whole cab files. | ||
46 | |||
47 | CabInfo cab1 = new CabInfo(diffInput1); | ||
48 | CabInfo cab2 = new CabInfo(diffInput2); | ||
49 | IList<CabFileInfo> cabFilesList1 = cab1.GetFiles(); | ||
50 | IList<CabFileInfo> cabFilesList2 = cab2.GetFiles(); | ||
51 | CabFileInfo[] cabFiles1 = new CabFileInfo[cabFilesList1.Count]; | ||
52 | CabFileInfo[] cabFiles2 = new CabFileInfo[cabFilesList2.Count]; | ||
53 | cabFilesList1.CopyTo(cabFiles1, 0); | ||
54 | cabFilesList2.CopyTo(cabFiles2, 0); | ||
55 | string[] files1 = new string[cabFiles1.Length]; | ||
56 | string[] files2 = new string[cabFiles2.Length]; | ||
57 | for(int i1 = 0; i1 < cabFiles1.Length; i1++) files1[i1] = cabFiles1[i1].Name; | ||
58 | for(int i2 = 0; i2 < cabFiles2.Length; i2++) files2[i2] = cabFiles2[i2].Name; | ||
59 | Array.Sort(files1, cabFiles1, caseInsComp); | ||
60 | Array.Sort(files2, cabFiles2, caseInsComp); | ||
61 | |||
62 | |||
63 | for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; ) | ||
64 | { | ||
65 | int comp; | ||
66 | if(i1 == files1.Length) | ||
67 | { | ||
68 | comp = 1; | ||
69 | } | ||
70 | else if(i2 == files2.Length) | ||
71 | { | ||
72 | comp = -1; | ||
73 | } | ||
74 | else | ||
75 | { | ||
76 | comp = caseInsComp.Compare(files1[i1], files2[i2]); | ||
77 | } | ||
78 | if(comp < 0) | ||
79 | { | ||
80 | diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]); | ||
81 | i1++; | ||
82 | difference = true; | ||
83 | } | ||
84 | else if(comp > 0) | ||
85 | { | ||
86 | diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]); | ||
87 | i2++; | ||
88 | difference = true; | ||
89 | } | ||
90 | else | ||
91 | { | ||
92 | string tempFile1 = Path.GetTempFileName(); | ||
93 | string tempFile2 = Path.GetTempFileName(); | ||
94 | cabFiles1[i1].CopyTo(tempFile1, true); | ||
95 | cabFiles2[i2].CopyTo(tempFile2, true); | ||
96 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options); | ||
97 | StringWriter sw = new StringWriter(); | ||
98 | if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory)) | ||
99 | { | ||
100 | diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]); | ||
101 | diffOutput.Write(sw.ToString()); | ||
102 | difference = true; | ||
103 | } | ||
104 | |||
105 | File.SetAttributes(tempFile1, File.GetAttributes(tempFile1) & ~FileAttributes.ReadOnly); | ||
106 | File.SetAttributes(tempFile2, File.GetAttributes(tempFile2) & ~FileAttributes.ReadOnly); | ||
107 | try | ||
108 | { | ||
109 | File.Delete(tempFile1); | ||
110 | File.Delete(tempFile2); | ||
111 | } | ||
112 | catch(IOException) | ||
113 | { | ||
114 | #if DEBUG | ||
115 | Console.WriteLine("Could not delete temporary files {0} and {1}", tempFile1, tempFile2); | ||
116 | #endif | ||
117 | } | ||
118 | i1++; | ||
119 | i2++; | ||
120 | } | ||
121 | } | ||
122 | |||
123 | return difference; | ||
124 | } | ||
125 | |||
126 | public virtual IDiffEngine Clone() | ||
127 | { | ||
128 | return new CabDiffEngine(); | ||
129 | } | ||
130 | } | ||
131 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Text; | ||
6 | |||
7 | namespace WixToolset.Dtf.Samples.DDiff | ||
8 | { | ||
9 | public class DDiff | ||
10 | { | ||
11 | public static void Usage(TextWriter w) | ||
12 | { | ||
13 | w.WriteLine("Usage: DDiff target1 target2 [options]"); | ||
14 | w.WriteLine("Example: DDiff d:\\dir1 d:\\dir2"); | ||
15 | w.WriteLine("Example: DDiff patch1.msp patch2.msp /patchtarget target.msi"); | ||
16 | w.WriteLine(); | ||
17 | w.WriteLine("Options:"); | ||
18 | w.WriteLine(" /o [filename] Output results to text file (UTF8)"); | ||
19 | w.WriteLine(" /p [package.msi] Diff patches relative to target MSI"); | ||
20 | } | ||
21 | |||
22 | public static int Main(string[] args) | ||
23 | { | ||
24 | if(args.Length < 2) | ||
25 | { | ||
26 | Usage(Console.Out); | ||
27 | return -1; | ||
28 | } | ||
29 | |||
30 | string input1 = args[0]; | ||
31 | string input2 = args[1]; | ||
32 | string[] options = new string[args.Length - 2]; | ||
33 | for(int i = 0; i < options.Length; i++) options[i] = args[i+2]; | ||
34 | |||
35 | TextWriter output = Console.Out; | ||
36 | |||
37 | for(int i = 0; i < options.Length - 1; i++) | ||
38 | { | ||
39 | switch(options[i].ToLower()) | ||
40 | { | ||
41 | case "/o": goto case "-output"; | ||
42 | case "-o": goto case "-output"; | ||
43 | case "/output": goto case "-output"; | ||
44 | case "-output": output = new StreamWriter(options[i+1], false, Encoding.UTF8); break; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | IDiffEngineFactory diffFactory = new BestQualityDiffEngineFactory(new IDiffEngine[] | ||
49 | { | ||
50 | new DirectoryDiffEngine(), | ||
51 | new FileDiffEngine(), | ||
52 | new VersionedFileDiffEngine(), | ||
53 | new TextFileDiffEngine(), | ||
54 | new MsiDiffEngine(), | ||
55 | new CabDiffEngine(), | ||
56 | new MspDiffEngine(), | ||
57 | }); | ||
58 | |||
59 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(input1, input2, options); | ||
60 | if(diffEngine != null) | ||
61 | { | ||
62 | bool different = diffEngine.GetDiff(input1, input2, options, output, "", diffFactory); | ||
63 | return different ? 1 : 0; | ||
64 | } | ||
65 | else | ||
66 | { | ||
67 | Console.Error.WriteLine("Dont know how to diff those inputs."); | ||
68 | return -1; | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Collections; | ||
6 | |||
7 | namespace WixToolset.Dtf.Samples.DDiff | ||
8 | { | ||
9 | public class DirectoryDiffEngine : IDiffEngine | ||
10 | { | ||
11 | public DirectoryDiffEngine() | ||
12 | { | ||
13 | } | ||
14 | |||
15 | public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
16 | { | ||
17 | if(diffInput1 != null && Directory.Exists(diffInput1) && | ||
18 | diffInput2 != null && Directory.Exists(diffInput2)) | ||
19 | { | ||
20 | return .70f; | ||
21 | } | ||
22 | else | ||
23 | { | ||
24 | return 0; | ||
25 | } | ||
26 | } | ||
27 | |||
28 | public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
29 | { | ||
30 | bool difference = false; | ||
31 | IComparer caseInsComp = CaseInsensitiveComparer.Default; | ||
32 | |||
33 | string[] files1 = Directory.GetFiles(diffInput1); | ||
34 | string[] files2 = Directory.GetFiles(diffInput2); | ||
35 | for(int i1 = 0; i1 < files1.Length; i1++) | ||
36 | { | ||
37 | files1[i1] = Path.GetFileName(files1[i1]); | ||
38 | } | ||
39 | for(int i2 = 0; i2 < files2.Length; i2++) | ||
40 | { | ||
41 | files2[i2] = Path.GetFileName(files2[i2]); | ||
42 | } | ||
43 | Array.Sort(files1, caseInsComp); | ||
44 | Array.Sort(files2, caseInsComp); | ||
45 | |||
46 | for(int i1 = 0, i2 = 0; i1 < files1.Length || i2 < files2.Length; ) | ||
47 | { | ||
48 | int comp; | ||
49 | if(i1 == files1.Length) | ||
50 | { | ||
51 | comp = 1; | ||
52 | } | ||
53 | else if(i2 == files2.Length) | ||
54 | { | ||
55 | comp = -1; | ||
56 | } | ||
57 | else | ||
58 | { | ||
59 | comp = caseInsComp.Compare(files1[i1], files2[i2]); | ||
60 | } | ||
61 | if(comp < 0) | ||
62 | { | ||
63 | diffOutput.WriteLine("{0}< {1}", linePrefix, files1[i1]); | ||
64 | i1++; | ||
65 | difference = true; | ||
66 | } | ||
67 | else if(comp > 0) | ||
68 | { | ||
69 | diffOutput.WriteLine("{0}> {1}", linePrefix, files2[i2]); | ||
70 | i2++; | ||
71 | difference = true; | ||
72 | } | ||
73 | else | ||
74 | { | ||
75 | string file1 = Path.Combine(diffInput1, files1[i1]); | ||
76 | string file2 = Path.Combine(diffInput2, files2[i2]); | ||
77 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(file1, file2, options); | ||
78 | StringWriter sw = new StringWriter(); | ||
79 | if(diffEngine.GetDiff(file1, file2, options, sw, linePrefix + " ", diffFactory)) | ||
80 | { | ||
81 | diffOutput.WriteLine("{0}{1}", linePrefix, files1[i1]); | ||
82 | diffOutput.Write(sw.ToString()); | ||
83 | difference = true; | ||
84 | } | ||
85 | i1++; | ||
86 | i2++; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | string[] dirs1 = Directory.GetDirectories(diffInput1); | ||
91 | string[] dirs2 = Directory.GetDirectories(diffInput2); | ||
92 | for(int i1 = 0; i1 < dirs1.Length; i1++) | ||
93 | { | ||
94 | dirs1[i1] = Path.GetFileName(dirs1[i1]); | ||
95 | } | ||
96 | for(int i2 = 0; i2 < dirs2.Length; i2++) | ||
97 | { | ||
98 | dirs2[i2] = Path.GetFileName(dirs2[i2]); | ||
99 | } | ||
100 | Array.Sort(dirs1, caseInsComp); | ||
101 | Array.Sort(dirs2, caseInsComp); | ||
102 | |||
103 | for(int i1 = 0, i2 = 0; i1 < dirs1.Length || i2 < dirs2.Length; ) | ||
104 | { | ||
105 | int comp; | ||
106 | if(i1 == dirs1.Length) | ||
107 | { | ||
108 | comp = 1; | ||
109 | } | ||
110 | else if(i2 == dirs2.Length) | ||
111 | { | ||
112 | comp = -1; | ||
113 | } | ||
114 | else | ||
115 | { | ||
116 | comp = caseInsComp.Compare(dirs1[i1], dirs2[i2]); | ||
117 | } | ||
118 | if(comp < 0) | ||
119 | { | ||
120 | diffOutput.WriteLine("{0}< {1}", linePrefix, dirs1[i1]); | ||
121 | i1++; | ||
122 | difference = true; | ||
123 | } | ||
124 | else if(comp > 0) | ||
125 | { | ||
126 | diffOutput.WriteLine("{0}> {1}", linePrefix, dirs2[i2]); | ||
127 | i2++; | ||
128 | difference = true; | ||
129 | } | ||
130 | else | ||
131 | { | ||
132 | string dir1 = Path.Combine(diffInput1, dirs1[i1]); | ||
133 | string dir2 = Path.Combine(diffInput2, dirs2[i2]); | ||
134 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(dir1, dir2, options); | ||
135 | StringWriter sw = new StringWriter(); | ||
136 | if(diffEngine.GetDiff(dir1, dir2, options, sw, linePrefix + " ", diffFactory)) | ||
137 | { | ||
138 | diffOutput.WriteLine("{0}{1}\\", linePrefix, dirs1[i1]); | ||
139 | diffOutput.Write(sw.ToString()); | ||
140 | difference = true; | ||
141 | } | ||
142 | i1++; | ||
143 | i2++; | ||
144 | } | ||
145 | } | ||
146 | return difference; | ||
147 | } | ||
148 | |||
149 | public virtual IDiffEngine Clone() | ||
150 | { | ||
151 | return new DirectoryDiffEngine(); | ||
152 | } | ||
153 | } | ||
154 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | |||
6 | namespace WixToolset.Dtf.Samples.DDiff | ||
7 | { | ||
8 | public class FileDiffEngine : IDiffEngine | ||
9 | { | ||
10 | public FileDiffEngine() | ||
11 | { | ||
12 | } | ||
13 | |||
14 | public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
15 | { | ||
16 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
17 | diffInput2 != null && File.Exists(diffInput2)) | ||
18 | { | ||
19 | return .10f; | ||
20 | } | ||
21 | else | ||
22 | { | ||
23 | return 0; | ||
24 | } | ||
25 | } | ||
26 | |||
27 | public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
28 | { | ||
29 | bool difference = false; | ||
30 | |||
31 | FileInfo file1 = new FileInfo(diffInput1); | ||
32 | FileInfo file2 = new FileInfo(diffInput2); | ||
33 | |||
34 | if(file1.Length != file2.Length) | ||
35 | { | ||
36 | diffOutput.WriteLine("{0}File size: {1} -> {2}", linePrefix, file1.Length, file2.Length); | ||
37 | difference = true; | ||
38 | } | ||
39 | else | ||
40 | { | ||
41 | FileStream stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read); | ||
42 | FileStream stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read); | ||
43 | |||
44 | byte[] buf1 = new byte[512]; | ||
45 | byte[] buf2 = new byte[512]; | ||
46 | |||
47 | while(!difference) | ||
48 | { | ||
49 | int count1 = stream1.Read(buf1, 0, buf1.Length); | ||
50 | int count2 = stream2.Read(buf2, 0, buf2.Length); | ||
51 | |||
52 | for(int i = 0; i < count1; i++) | ||
53 | { | ||
54 | if(i == count2 || buf1[i] != buf2[i]) | ||
55 | { | ||
56 | difference = true; | ||
57 | break; | ||
58 | } | ||
59 | } | ||
60 | if(count1 < buf1.Length) // EOF | ||
61 | { | ||
62 | break; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | stream1.Close(); | ||
67 | stream2.Close(); | ||
68 | |||
69 | if(difference) | ||
70 | { | ||
71 | diffOutput.WriteLine("{0}Files differ.", linePrefix); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | return difference; | ||
76 | } | ||
77 | |||
78 | public virtual IDiffEngine Clone() | ||
79 | { | ||
80 | return new FileDiffEngine(); | ||
81 | } | ||
82 | } | ||
83 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Collections; | ||
6 | |||
7 | namespace WixToolset.Dtf.Samples.DDiff | ||
8 | { | ||
9 | public interface IDiffEngine | ||
10 | { | ||
11 | float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory); | ||
12 | |||
13 | bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory); | ||
14 | |||
15 | IDiffEngine Clone(); | ||
16 | } | ||
17 | |||
18 | public interface IDiffEngineFactory | ||
19 | { | ||
20 | IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options); | ||
21 | } | ||
22 | |||
23 | public class BestQualityDiffEngineFactory : IDiffEngineFactory | ||
24 | { | ||
25 | public virtual IDiffEngine GetDiffEngine(string diffInput1, string diffInput2, string[] options) | ||
26 | { | ||
27 | float bestDiffQuality = 0; | ||
28 | IDiffEngine bestDiffEngine = null; | ||
29 | |||
30 | foreach(IDiffEngine diffEngine in diffEngines) | ||
31 | { | ||
32 | float diffQuality = diffEngine.GetDiffQuality(diffInput1, diffInput2, options, this); | ||
33 | if(diffQuality > bestDiffQuality) | ||
34 | { | ||
35 | bestDiffQuality = diffQuality; | ||
36 | bestDiffEngine = diffEngine; | ||
37 | } | ||
38 | } | ||
39 | return (bestDiffEngine != null ? bestDiffEngine.Clone() : null); | ||
40 | } | ||
41 | |||
42 | public BestQualityDiffEngineFactory() : this(null) { } | ||
43 | public BestQualityDiffEngineFactory(IDiffEngine[] diffEngines) | ||
44 | { | ||
45 | this.diffEngines = (diffEngines != null ? new ArrayList(diffEngines) : new ArrayList()); | ||
46 | } | ||
47 | |||
48 | protected IList diffEngines; | ||
49 | |||
50 | public virtual void Add(IDiffEngine diffEngine) | ||
51 | { | ||
52 | diffEngines.Add(diffEngine); | ||
53 | } | ||
54 | |||
55 | public virtual void Remove(IDiffEngine diffEngine) | ||
56 | { | ||
57 | diffEngines.Remove(diffEngine); | ||
58 | } | ||
59 | |||
60 | public IList DiffEngines | ||
61 | { | ||
62 | get | ||
63 | { | ||
64 | return ArrayList.ReadOnly(diffEngines); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Collections; | ||
6 | using System.Collections.Generic; | ||
7 | using WixToolset.Dtf.WindowsInstaller; | ||
8 | using WixToolset.Dtf.WindowsInstaller.Package; | ||
9 | |||
10 | namespace WixToolset.Dtf.Samples.DDiff | ||
11 | { | ||
12 | public class MsiDiffEngine : IDiffEngine | ||
13 | { | ||
14 | public MsiDiffEngine() | ||
15 | { | ||
16 | } | ||
17 | |||
18 | protected bool IsMsiDatabase(string file) | ||
19 | { | ||
20 | // TODO: use something smarter? | ||
21 | switch(Path.GetExtension(file).ToLower()) | ||
22 | { | ||
23 | case ".msi": return true; | ||
24 | case ".msm": return true; | ||
25 | case ".pcp": return true; | ||
26 | default : return false; | ||
27 | } | ||
28 | } | ||
29 | |||
30 | protected bool IsMspPatch(string file) | ||
31 | { | ||
32 | // TODO: use something smarter? | ||
33 | switch(Path.GetExtension(file).ToLower()) | ||
34 | { | ||
35 | case ".msp": return true; | ||
36 | default : return false; | ||
37 | } | ||
38 | } | ||
39 | |||
40 | public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
41 | { | ||
42 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
43 | diffInput2 != null && File.Exists(diffInput2) && | ||
44 | (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2))) | ||
45 | { | ||
46 | return .70f; | ||
47 | } | ||
48 | else if(diffInput1 != null && File.Exists(diffInput1) && | ||
49 | diffInput2 != null && File.Exists(diffInput2) && | ||
50 | (IsMspPatch(diffInput1) || IsMspPatch(diffInput2))) | ||
51 | { | ||
52 | return .60f; | ||
53 | } | ||
54 | else | ||
55 | { | ||
56 | return 0; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
61 | { | ||
62 | bool difference = false; | ||
63 | Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly); | ||
64 | Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly); | ||
65 | |||
66 | if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
67 | if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
68 | if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
69 | |||
70 | db1.Close(); | ||
71 | db2.Close(); | ||
72 | return difference; | ||
73 | } | ||
74 | |||
75 | protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
76 | { | ||
77 | bool difference = false; | ||
78 | |||
79 | SummaryInfo summInfo1 = db1.SummaryInfo; | ||
80 | SummaryInfo summInfo2 = db2.SummaryInfo; | ||
81 | if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; } | ||
82 | if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; } | ||
83 | if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; } | ||
84 | if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; } | ||
85 | if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; } | ||
86 | if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; } | ||
87 | if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; } | ||
88 | if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; } | ||
89 | if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; } | ||
90 | if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; } | ||
91 | if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; } | ||
92 | if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; } | ||
93 | if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; } | ||
94 | if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; } | ||
95 | if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; } | ||
96 | if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; } | ||
97 | if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; } | ||
98 | summInfo1.Close(); | ||
99 | summInfo2.Close(); | ||
100 | |||
101 | return difference; | ||
102 | } | ||
103 | |||
104 | protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
105 | { | ||
106 | bool difference = false; | ||
107 | |||
108 | string tempFile = Path.GetTempFileName(); | ||
109 | if(db2.GenerateTransform(db1, tempFile)) | ||
110 | { | ||
111 | difference = true; | ||
112 | |||
113 | Database db = db1; | ||
114 | db.ViewTransform(tempFile); | ||
115 | |||
116 | string row, column, change; | ||
117 | using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " + | ||
118 | "FROM `_TransformView` ORDER BY `Table`, `Row`")) | ||
119 | { | ||
120 | view.Execute(); | ||
121 | |||
122 | foreach (Record rec in view) using (rec) | ||
123 | { | ||
124 | column = String.Format("{0} {1}", rec[1], rec[2]); | ||
125 | change = ""; | ||
126 | if (rec.IsNull(3)) | ||
127 | { | ||
128 | row = "<DDL>"; | ||
129 | if (!rec.IsNull(4)) | ||
130 | { | ||
131 | change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4)); | ||
132 | } | ||
133 | } | ||
134 | else | ||
135 | { | ||
136 | row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]"; | ||
137 | if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE") | ||
138 | { | ||
139 | column = String.Format("{0}.{1}", rec[1], rec[2]); | ||
140 | change = "{" + rec[5] + "}->{" + rec[4] + "}"; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change); | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | File.Delete(tempFile); | ||
149 | |||
150 | return difference; | ||
151 | } | ||
152 | |||
153 | private string DecodeColDef(int colDef) | ||
154 | { | ||
155 | const int icdLong = 0x0000; | ||
156 | const int icdShort = 0x0400; | ||
157 | const int icdObject = 0x0800; | ||
158 | const int icdString = 0x0C00; | ||
159 | const int icdTypeMask = 0x0F00; | ||
160 | const int icdNullable = 0x1000; | ||
161 | const int icdPrimaryKey = 0x2000; | ||
162 | |||
163 | string def = ""; | ||
164 | switch(colDef & (icdTypeMask)) | ||
165 | { | ||
166 | case icdLong : def = "LONG"; break; | ||
167 | case icdShort : def = "SHORT"; break; | ||
168 | case icdObject: def = "OBJECT"; break; | ||
169 | case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break; | ||
170 | } | ||
171 | if((colDef & icdNullable) != 0) | ||
172 | { | ||
173 | def = def + " NOT NULL"; | ||
174 | } | ||
175 | if((colDef & icdPrimaryKey) != 0) | ||
176 | { | ||
177 | def = def + " PRIMARY KEY"; | ||
178 | } | ||
179 | return def; | ||
180 | } | ||
181 | |||
182 | protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
183 | { | ||
184 | bool difference = false; | ||
185 | |||
186 | IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); | ||
187 | IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); | ||
188 | string[] streams1 = new string[streams1List.Count]; | ||
189 | string[] streams2 = new string[streams2List.Count]; | ||
190 | streams1List.CopyTo(streams1, 0); | ||
191 | streams2List.CopyTo(streams2, 0); | ||
192 | |||
193 | IComparer caseInsComp = CaseInsensitiveComparer.Default; | ||
194 | Array.Sort(streams1, caseInsComp); | ||
195 | Array.Sort(streams2, caseInsComp); | ||
196 | |||
197 | for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; ) | ||
198 | { | ||
199 | int comp; | ||
200 | if (i1 == streams1.Length) | ||
201 | { | ||
202 | comp = 1; | ||
203 | } | ||
204 | else if (i2 == streams2.Length) | ||
205 | { | ||
206 | comp = -1; | ||
207 | } | ||
208 | else | ||
209 | { | ||
210 | comp = caseInsComp.Compare(streams1[i1], streams2[i2]); | ||
211 | } | ||
212 | if(comp < 0) | ||
213 | { | ||
214 | diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]); | ||
215 | i1++; | ||
216 | difference = true; | ||
217 | } | ||
218 | else if(comp > 0) | ||
219 | { | ||
220 | diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]); | ||
221 | i2++; | ||
222 | difference = true; | ||
223 | } | ||
224 | else | ||
225 | { | ||
226 | if(streams1[i1] != ("" + ((char)5) + "SummaryInformation")) | ||
227 | { | ||
228 | string tempFile1 = Path.GetTempFileName(); | ||
229 | string tempFile2 = Path.GetTempFileName(); | ||
230 | |||
231 | using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1]))) | ||
232 | { | ||
233 | view.Execute(); | ||
234 | |||
235 | using (Record rec = view.Fetch()) | ||
236 | { | ||
237 | rec.GetStream(1, tempFile1); | ||
238 | } | ||
239 | } | ||
240 | |||
241 | using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2]))) | ||
242 | { | ||
243 | view.Execute(); | ||
244 | |||
245 | using (Record rec = view.Fetch()) | ||
246 | { | ||
247 | rec.GetStream(1, tempFile2); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options); | ||
252 | StringWriter sw = new StringWriter(); | ||
253 | if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory)) | ||
254 | { | ||
255 | diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]); | ||
256 | diffOutput.Write(sw.ToString()); | ||
257 | difference = true; | ||
258 | } | ||
259 | |||
260 | File.Delete(tempFile1); | ||
261 | File.Delete(tempFile2); | ||
262 | } | ||
263 | i1++; | ||
264 | i2++; | ||
265 | } | ||
266 | } | ||
267 | |||
268 | return difference; | ||
269 | } | ||
270 | |||
271 | public virtual IDiffEngine Clone() | ||
272 | { | ||
273 | return new MsiDiffEngine(); | ||
274 | } | ||
275 | } | ||
276 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using WixToolset.Dtf.WindowsInstaller; | ||
6 | using WixToolset.Dtf.WindowsInstaller.Package; | ||
7 | |||
8 | namespace WixToolset.Dtf.Samples.DDiff | ||
9 | { | ||
10 | public class MspDiffEngine : MsiDiffEngine | ||
11 | { | ||
12 | public MspDiffEngine() | ||
13 | { | ||
14 | } | ||
15 | |||
16 | private string GetPatchTargetOption(string[] options) | ||
17 | { | ||
18 | for(int i = 0; i < options.Length - 1; i++) | ||
19 | { | ||
20 | switch(options[i].ToLower()) | ||
21 | { | ||
22 | case "/p": goto case "-patchtarget"; | ||
23 | case "-p": goto case "-patchtarget"; | ||
24 | case "/patchtarget": goto case "-patchtarget"; | ||
25 | case "-patchtarget": return options[i+1]; | ||
26 | } | ||
27 | } | ||
28 | return null; | ||
29 | } | ||
30 | |||
31 | public override float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
32 | { | ||
33 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
34 | diffInput2 != null && File.Exists(diffInput2) && | ||
35 | GetPatchTargetOption(options) != null && | ||
36 | (IsMspPatch(diffInput1) && IsMspPatch(diffInput2))) | ||
37 | { | ||
38 | return .80f; | ||
39 | } | ||
40 | else if(diffInput1 != null && File.Exists(diffInput1) && | ||
41 | diffInput2 != null && File.Exists(diffInput2) && | ||
42 | GetPatchTargetOption(options) == null && | ||
43 | (IsMspPatch(diffInput1) && IsMsiDatabase(diffInput2)) || | ||
44 | (IsMsiDatabase(diffInput1) && IsMspPatch(diffInput2))) | ||
45 | { | ||
46 | return .75f; | ||
47 | } | ||
48 | else | ||
49 | { | ||
50 | return 0; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | public override bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
55 | { | ||
56 | bool difference = false; | ||
57 | |||
58 | InstallPackage db1, db2; | ||
59 | if(IsMspPatch(diffInput1)) | ||
60 | { | ||
61 | string patchTargetDbFile = GetPatchTargetOption(options); | ||
62 | if(patchTargetDbFile == null) patchTargetDbFile = diffInput2; | ||
63 | string tempPatchedDbFile = Path.GetTempFileName(); | ||
64 | File.Copy(patchTargetDbFile, tempPatchedDbFile, true); | ||
65 | File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly); | ||
66 | db1 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct); | ||
67 | db1.ApplyPatch(new PatchPackage(diffInput1), null); | ||
68 | db1.Commit(); | ||
69 | } | ||
70 | else | ||
71 | { | ||
72 | db1 = new InstallPackage(diffInput1, DatabaseOpenMode.ReadOnly); | ||
73 | } | ||
74 | if(IsMspPatch(diffInput2)) | ||
75 | { | ||
76 | string patchTargetDbFile = GetPatchTargetOption(options); | ||
77 | if(patchTargetDbFile == null) patchTargetDbFile = diffInput1; | ||
78 | string tempPatchedDbFile = Path.GetTempFileName(); | ||
79 | File.Copy(patchTargetDbFile, tempPatchedDbFile, true); | ||
80 | File.SetAttributes(tempPatchedDbFile, File.GetAttributes(tempPatchedDbFile) & ~System.IO.FileAttributes.ReadOnly); | ||
81 | db2 = new InstallPackage(tempPatchedDbFile, DatabaseOpenMode.Direct); | ||
82 | db2.ApplyPatch(new PatchPackage(diffInput2), null); | ||
83 | db2.Commit(); | ||
84 | } | ||
85 | else | ||
86 | { | ||
87 | db2 = new InstallPackage(diffInput2, DatabaseOpenMode.ReadOnly); | ||
88 | } | ||
89 | |||
90 | if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
91 | if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
92 | if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
93 | |||
94 | db1.Close(); | ||
95 | db2.Close(); | ||
96 | |||
97 | try | ||
98 | { | ||
99 | if(IsMspPatch(diffInput1)) File.Delete(db1.FilePath); | ||
100 | if(IsMspPatch(diffInput1)) File.Delete(db2.FilePath); | ||
101 | } | ||
102 | catch(IOException) | ||
103 | { | ||
104 | #if DEBUG | ||
105 | Console.WriteLine("Could not delete temporary files {0} and {1}", db1.FilePath, db2.FilePath); | ||
106 | #endif | ||
107 | } | ||
108 | |||
109 | if(IsMspPatch(diffInput1) && IsMspPatch(diffInput2)) | ||
110 | { | ||
111 | Database dbp1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly); | ||
112 | Database dbp2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly); | ||
113 | |||
114 | if(GetStreamsDiff(dbp1, dbp2, options, diffOutput, linePrefix, diffFactory)) difference = true; | ||
115 | dbp1.Close(); | ||
116 | dbp2.Close(); | ||
117 | } | ||
118 | |||
119 | return difference; | ||
120 | } | ||
121 | |||
122 | public override IDiffEngine Clone() | ||
123 | { | ||
124 | return new MspDiffEngine(); | ||
125 | } | ||
126 | } | ||
127 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Diagnostics; | ||
6 | |||
7 | namespace WixToolset.Dtf.Samples.DDiff | ||
8 | { | ||
9 | public class TextFileDiffEngine : IDiffEngine | ||
10 | { | ||
11 | public TextFileDiffEngine() | ||
12 | { | ||
13 | } | ||
14 | |||
15 | private bool IsTextFile(string file) | ||
16 | { | ||
17 | // Guess whether this is a text file by reading the first few bytes and checking for non-ascii chars. | ||
18 | |||
19 | bool isText = true; | ||
20 | FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
21 | byte[] buf = new byte[256]; | ||
22 | int count = stream.Read(buf, 0, buf.Length); | ||
23 | for(int i = 0; i < count; i++) | ||
24 | { | ||
25 | if((buf[i] & 0x80) != 0) | ||
26 | { | ||
27 | isText = false; | ||
28 | break; | ||
29 | } | ||
30 | } | ||
31 | stream.Close(); | ||
32 | return isText; | ||
33 | } | ||
34 | |||
35 | public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
36 | { | ||
37 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
38 | diffInput2 != null && File.Exists(diffInput2) && | ||
39 | (IsTextFile(diffInput1) && IsTextFile(diffInput2))) | ||
40 | { | ||
41 | return .70f; | ||
42 | } | ||
43 | else | ||
44 | { | ||
45 | return 0; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
50 | { | ||
51 | try | ||
52 | { | ||
53 | bool difference = false; | ||
54 | ProcessStartInfo psi = new ProcessStartInfo("diff.exe"); | ||
55 | psi.Arguments = String.Format("\"{0}\" \"{1}\"", diffInput1, diffInput2); | ||
56 | psi.WorkingDirectory = null; | ||
57 | psi.UseShellExecute = false; | ||
58 | psi.WindowStyle = ProcessWindowStyle.Hidden; | ||
59 | psi.RedirectStandardOutput = true; | ||
60 | Process proc = Process.Start(psi); | ||
61 | |||
62 | string line; | ||
63 | while((line = proc.StandardOutput.ReadLine()) != null) | ||
64 | { | ||
65 | diffOutput.WriteLine("{0}{1}", linePrefix, line); | ||
66 | difference = true; | ||
67 | } | ||
68 | |||
69 | proc.WaitForExit(); | ||
70 | return difference; | ||
71 | } | ||
72 | catch(System.ComponentModel.Win32Exception) // If diff.exe is not found, just compare the bytes | ||
73 | { | ||
74 | return new FileDiffEngine().GetDiff(diffInput1, diffInput2, options, diffOutput, linePrefix, diffFactory); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | public IDiffEngine Clone() | ||
79 | { | ||
80 | return new TextFileDiffEngine(); | ||
81 | } | ||
82 | } | ||
83 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using WixToolset.Dtf.WindowsInstaller; | ||
6 | |||
7 | namespace WixToolset.Dtf.Samples.DDiff | ||
8 | { | ||
9 | public class VersionedFileDiffEngine : IDiffEngine | ||
10 | { | ||
11 | public VersionedFileDiffEngine() | ||
12 | { | ||
13 | } | ||
14 | |||
15 | private bool IsVersionedFile(string file) | ||
16 | { | ||
17 | return Installer.GetFileVersion(file) != ""; | ||
18 | } | ||
19 | |||
20 | public float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) | ||
21 | { | ||
22 | if(diffInput1 != null && File.Exists(diffInput1) && | ||
23 | diffInput2 != null && File.Exists(diffInput2) && | ||
24 | (IsVersionedFile(diffInput1) || IsVersionedFile(diffInput2))) | ||
25 | { | ||
26 | return .20f; | ||
27 | } | ||
28 | else | ||
29 | { | ||
30 | return 0; | ||
31 | } | ||
32 | } | ||
33 | |||
34 | public bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) | ||
35 | { | ||
36 | bool difference = false; | ||
37 | |||
38 | string ver1 = Installer.GetFileVersion(diffInput1); | ||
39 | string ver2 = Installer.GetFileVersion(diffInput2); | ||
40 | |||
41 | if(ver1 != ver2) | ||
42 | { | ||
43 | diffOutput.WriteLine("{0}File version: {1} -> {2}", linePrefix, ver1, ver2); | ||
44 | difference = true; | ||
45 | } | ||
46 | else | ||
47 | { | ||
48 | FileStream stream1 = new FileStream(diffInput1, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
49 | FileStream stream2 = new FileStream(diffInput2, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
50 | |||
51 | byte[] buf1 = new byte[512]; | ||
52 | byte[] buf2 = new byte[512]; | ||
53 | |||
54 | while(!difference) | ||
55 | { | ||
56 | int count1 = stream1.Read(buf1, 0, buf1.Length); | ||
57 | int count2 = stream2.Read(buf2, 0, buf2.Length); | ||
58 | |||
59 | for(int i = 0; i < count1; i++) | ||
60 | { | ||
61 | if(i == count2 || buf1[i] != buf2[i]) | ||
62 | { | ||
63 | difference = true; | ||
64 | break; | ||
65 | } | ||
66 | } | ||
67 | if(count1 < buf1.Length) // EOF | ||
68 | { | ||
69 | break; | ||
70 | } | ||
71 | } | ||
72 | |||
73 | stream1.Close(); | ||
74 | stream2.Close(); | ||
75 | |||
76 | if(difference) | ||
77 | { | ||
78 | diffOutput.WriteLine("{0}File versions match but bits differ.", linePrefix); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | return difference; | ||
83 | } | ||
84 | |||
85 | public IDiffEngine Clone() | ||
86 | { | ||
87 | return new VersionedFileDiffEngine(); | ||
88 | } | ||
89 | } | ||
90 | } | ||
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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
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> > | ||
16 | <a href="samples.htm">Samples</a> > | ||
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 <directory> <package.cab> | ||
26 | Usage: XPack /P <archive.cab> <directory> | ||
27 | Usage: XPack /P <archive.zip> <directory> | ||
28 | |||
29 | Packs all files in a directory tree into an archive, | ||
30 | using either the cab or zip format. Any existing archive | ||
31 | with the same name will be overwritten. | ||
32 | |||
33 | |||
34 | Usage: XPack /U <archive.cab> <directory> | ||
35 | Usage: XPack /U <archive.zip> <directory> | ||
36 | |||
37 | Unpacks all files from a cab or zip archive to the | ||
38 | specified directory. Any existing files with the same | ||
39 | names will be overwritten.</font></pre> | ||
40 | </p> | ||
41 | <p><br/></p> | ||
42 | |||
43 | </div> | ||
44 | |||
45 | <div id="footer"> | ||
46 | <p /> | ||
47 | Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation"> | ||
48 | wix-users@lists.sourceforge.net</a> | ||
49 | |||
50 | <script type="text/javascript"> | ||
51 | var HT_mailLink = document.getElementById("HT_MailLink"); | ||
52 | var HT_mailLinkText = HT_mailLink.innerHTML; | ||
53 | HT_mailLink.href += ": " + document.title; | ||
54 | HT_mailLink.innerHTML = HT_mailLinkText; | ||
55 | </script> | ||
56 | |||
57 | <p /> | ||
58 | |||
59 | </div> | ||
60 | </div> | ||
61 | |||
62 | </body> | ||
63 | </html> | ||
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> > | ||
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. 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. 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. 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. 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. 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. 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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
17 | <a href="writingcas.htm">Writing CAs</a> > | ||
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 | <?xml version="1.0" encoding="utf-8" ?> | ||
30 | <configuration> | ||
31 | <startup> | ||
32 | <supportedRuntime version="v2.0.50727"/> | ||
33 | </startup> | ||
34 | </configuration></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> > | ||
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. 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. 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. 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. 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. Create a record with one field containing the new binary value.</p> | ||
69 | <p>4. 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. <a href="DTFAPI.chm::/html/M_Microsoft_Deployment_WindowsInstaller_Database_Commit.htm">Commit</a> the Database.</p> | ||
73 | <p>6. <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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
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> > | ||
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> > | ||
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 /> <br /> <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 "released" 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("") to session.Message(User,""), | ||
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 & 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 & 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 "error 0"</li> | ||
325 | <li>Added optimization for setting a Record field to a MemoryStream</li> | ||
326 | <li>Record.GetStream is capable of extracting substorages</li> | ||
327 | <li>Moved InstallPath class to Microsoft.WindowsInstaller.Package assembly</li> | ||
328 | </ul></li> | ||
329 | <li>Msi\FilePatch: added new project<ul> | ||
330 | <li>Binary file patch API wrapper</li> | ||
331 | </ul></li> | ||
332 | <li>Msi\Package: added new project<ul> | ||
333 | <li>Helper classes for working with MSI and MSP packages</li> | ||
334 | </ul></li> | ||
335 | <li>Cab: some minor bugfixes<ul> | ||
336 | <li>Cabinet.Extract(stream, name) threw a NullReferenceException if the file | ||
337 | didn't exist in the cab -- now it returns null</li> | ||
338 | <li>CabinetInfo.CompressFileSet() was broken -- fixed</li> | ||
339 | <li>If a Cabinet callback throws an exception, it is propogated as the | ||
340 | inner-exception of the CabinetException</li> | ||
341 | </ul></li> | ||
342 | <li>Samples\WiFile: added new sample<ul> | ||
343 | <li>Demonstrates some features of InstallPackage class in Msi\Package project</li> | ||
344 | </ul></li> | ||
345 | </ul> | ||
346 | |||
347 | <hr size="2"/> | ||
348 | <h3><b>2003-03-20</b></h3> | ||
349 | |||
350 | Documentation!<ul> | ||
351 | <li>Msi and Cab sources include complete C# XML documentation.</li> | ||
352 | <li>Msi and Cab makefiles generate XML documentation files.</li> | ||
353 | <li>Reference CHM compiled from XML documentation with NDoc.</li> | ||
354 | </ul> | ||
355 | |||
356 | <p>I am aware that exceptions are still not documented in most areas. | ||
357 | Other than that, feel free to send me a note if it's still not clear | ||
358 | how to use parts of the API after reading the documentation.</p> | ||
359 | |||
360 | <p>Version is still 1.1 because there are no code changes in this release.</p> | ||
361 | |||
362 | <hr size="2"/> | ||
363 | <h3><b>2003-03-13</b></h3> | ||
364 | |||
365 | <ul> | ||
366 | <li>Msi: lots of small improvements for usability and consistency<ul> | ||
367 | <li>Reworked ExternalUIHandler support</li> | ||
368 | <li>Added Installer properties/methods:<ul> | ||
369 | <li>Components</li> | ||
370 | <li>ComponentClients()</li> | ||
371 | <li>ComponentState()</li> | ||
372 | <li>ComponentPath()</li> | ||
373 | <li>EnableLog()</li> | ||
374 | </ul></li> | ||
375 | <li>Added Session.EvaluateCondition() method</li> | ||
376 | <li>Improved exception-handling in many methods in Installer, Database, | ||
377 | & Session classes</li> | ||
378 | <li>Added extensive XML doc-comments to Installer, Database, View, | ||
379 | & Session classes</li> | ||
380 | <li>A few breaking changes:<ul> | ||
381 | <li>View.ModifyMode enumeration moved outside View and | ||
382 | renamed ViewModifyMode</li> | ||
383 | <li>InstallLogMode enumeration renamed to InstallLogModes | ||
384 | (naming convention for bitfields)</li> | ||
385 | <li>Record constructor takes arbitrary number of parameters</li> | ||
386 | </ul></li> | ||
387 | </ul></li> | ||
388 | <li>AugmentIL: almost completely rewritten<ul> | ||
389 | <li>Ildasm/ilasm steps are built-in<ul> | ||
390 | <li>The round-trip can be done in one step</li> | ||
391 | <li>IL source input/output is still supported</li> | ||
392 | </ul></li> | ||
393 | <li>Never throws an unhandled exception</li> | ||
394 | <li>Organized command-line options, consistent with other .NET tools</li> | ||
395 | <li>Uses a plugin architecture to allow additional augmentations</li> | ||
396 | </ul></li> | ||
397 | <li>CAProxy: Added AIL_CAProxy.cs - AugmentIL plugin generates CA proxy methods</li> | ||
398 | |||
399 | <li>SampleCAs: Updated makefile for new AugmentIL usage</li> | ||
400 | </ul> | ||
401 | |||
402 | <hr size="2"/> | ||
403 | <h3><b>2003-01-16</b></h3> | ||
404 | |||
405 | <ul> | ||
406 | <li>ReadMe.htm: Added section on writing managed CAs</li> | ||
407 | <li>SampleCAs: Added missing reference to System.Windows.Forms.dll to the makefile</li> | ||
408 | <li>AugmentIL: Added specific warning messages for when CA method has wrong signature</li> | ||
409 | <li>Put sources in Toolbox-hosted Source Depot.</li> | ||
410 | </ul> | ||
411 | |||
412 | <hr size="2"/> | ||
413 | <h3><b>2003-01-14</b></h3> | ||
414 | Initial posting to http://toolbox | ||
415 | |||
416 | <p> </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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
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> </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> > | ||
16 | <a href="samples.htm">Samples</a> > | ||
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 | "In Subtree" 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 "Find" again will continue the same search from that | ||
54 | point (unless you uncheck the "Continue" 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> > | ||
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> > | ||
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. 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. 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. 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. 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. 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> > | ||
16 | <a href="samples.htm">Samples</a> > | ||
17 | <span class="nolink">DDiff</span> | ||
18 | </span> | ||
19 | </div> | ||
20 | </div> | ||
21 | <div id="main"> | ||
22 | <div id="header"> | ||
23 | </div> | ||
24 | <div class="summary"> | ||
25 | <h2>MSI, MSP, CAB Diff Tool</h2> | ||
26 | |||
27 | <p><pre><font face="Consolas, Courier New">Usage: DDiff target1 target2 [options] | ||
28 | Example: DDiff d:\dir1 d:\dir2 | ||
29 | Example: DDiff setup1.msi setup2.msi | ||
30 | Example: DDiff patch1.msp patch2.msp -patchtarget target.msi | ||
31 | Example: DDiff package1.cab package2.cab | ||
32 | |||
33 | Options: | ||
34 | /o [filename] Output results to text file (UTF8) | ||
35 | /p [package.msi] Diff patches relative to target MSI</font></pre> | ||
36 | </p> | ||
37 | <p><br/></p> | ||
38 | |||
39 | <p>The following types of inputs can be diffed: | ||
40 | <ul> | ||
41 | <li><b>Directories</b>: files and subdirectories are compared.</li> | ||
42 | <li><b>Cab files</b>: internal file list and files are compared.</li> | ||
43 | <li><b>MSI/MSM database files</b>: summary info, tables, and embedded binary and cab streams are compared.</li> | ||
44 | <li><b>MSP files</b>: summary info and embedded file cab are compared. When a patch target MSI is provided, the MSP's tables are also compared.</li> | ||
45 | <li><b>Versioned files</b>: Win32 file version is compared.</li> | ||
46 | <li><b>Text files</b>: if diff.exe is in the path, it is used to get a line-by-line diff.</li> | ||
47 | <li><b>Other files</b>: file size and bytes are compared.</li> | ||
48 | </ul> | ||
49 | All processing is done recursively. So a versioned file within a cab within an MSI within a directory will have meaningful diff results.</p> | ||
50 | |||
51 | <p><br/></p> | ||
52 | </div> | ||
53 | <div id="footer"> | ||
54 | <p /> | ||
55 | Send comments on this topic to <a id="HT_MailLink" href="mailto:wix-users%40lists.sourceforge.net?Subject=Deployment Tools Foundation Documentation"> | ||
56 | wix-users@lists.sourceforge.net</a> | ||
57 | |||
58 | <script type="text/javascript"> | ||
59 | var HT_mailLink = document.getElementById("HT_MailLink"); | ||
60 | var HT_mailLinkText = HT_mailLink.innerHTML; | ||
61 | HT_mailLink.href += ": " + document.title; | ||
62 | HT_mailLink.innerHTML = HT_mailLinkText; | ||
63 | </script> | ||
64 | |||
65 | <p /> | ||
66 | |||
67 | </div> | ||
68 | </div> | ||
69 | |||
70 | </body> | ||
71 | </html> | ||
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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
17 | <a href="writingcas.htm">Writing CAs</a> > | ||
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. 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> > | ||
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> > | ||
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> > | ||
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> <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"> 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 & | ||
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> </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> > | ||
16 | <a href="samples.htm">Samples</a> > | ||
17 | <span class="nolink">WiFile</span> | ||
18 | </span> | ||
19 | </div> | ||
20 | </div> | ||
21 | <div id="main"> | ||
22 | <div id="header"> | ||
23 | </div> | ||
24 | <div class="summary"> | ||
25 | <p><pre><font face="Consolas, Courier New">Usage: WiFile.exe package.msi /l [filename,filename2,...] | ||
26 | Usage: WiFile.exe package.msi /x [filename,filename2,...] | ||
27 | Usage: WiFile.exe package.msi /u [filename,filename2,...] | ||
28 | |||
29 | Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM. | ||
30 | Files are extracted using their source path relative to the package. | ||
31 | Specified filenames do not include paths. | ||
32 | Filenames may be a pattern such as *.exe or file?.dll</font></pre> | ||
33 | </p> | ||
34 | <p><br/></p> | ||
35 | |||
36 | <h4>Example</h4> | ||
37 | <p>The most powerful use of WiFile.exe is to do a round-trip update of files in a | ||
38 | compressed MSI/MSM package. It works like this:<ol> | ||
39 | <li>Extract specific file(s) or all files from the package: | ||
40 | <tt>WiFile.exe package.msi /x *</tt></li> | ||
41 | <li>The files are now expanded into their directory structure. You can edit/update | ||
42 | the files however you like.</li> | ||
43 | <li>Update the package with the changed files: <tt>WiFile.exe package.msi /u *</tt> | ||
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> > | ||
16 | <a href="managedcas.htm">Managed CAs</a> > | ||
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 | "name" 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] | ||
2 | Auto Index=Yes | ||
3 | Compatibility=1.1 or later | ||
4 | Compiled file=DTF.chm | ||
5 | Contents file=DTF.hhc | ||
6 | Default Window=DTF | ||
7 | Default topic=Content\about.htm | ||
8 | Display compile progress=Yes | ||
9 | Error log file=DTFhelp.log | ||
10 | Full-text search=Yes | ||
11 | Index file=DTF.hhk | ||
12 | Language=0x409 English (United States) | ||
13 | Title=Deployment Tools Foundation | ||
14 | |||
15 | [WINDOWS] | ||
16 | DTF="Deployment Tools Foundation","DTF.hhc","DTF.hhk","Content\about.htm","Content\about.htm",,,,,0x22520,,0x384e,[143,72,937,601],,,,,,,0 | ||
17 | |||
18 | |||
19 | [FILES] | ||
20 | styles\presentation.css | ||
21 | Content\about.htm | ||
22 | Content\whatsnew.htm | ||
23 | Content\dependencies.htm | ||
24 | Content\using.htm | ||
25 | DTF.hhc | ||
26 | DTF.hhk | ||
27 | Content\samples.htm | ||
28 | Content\support.htm | ||
29 | Content\history.htm | ||
30 | Content\inventory.htm | ||
31 | Content\wifile.htm | ||
32 | Content\cabpack.htm | ||
33 | Content\powerdiff.htm | ||
34 | Content\cabs.htm | ||
35 | Content\databases.htm | ||
36 | Content\packages.htm | ||
37 | Content\managedcas.htm | ||
38 | Content\samplecas.htm | ||
39 | Content\writingcas.htm | ||
40 | Content\buildingcas.htm | ||
41 | Content\debuggingcas.htm | ||
42 | Content\caproxy.htm | ||
43 | Content\caconfig.htm | ||
44 | Content\installutil.htm | ||
45 | |||
46 | [MERGE FILES] | ||
47 | DTFAPI.chm | ||
48 | |||
49 | [INFOTYPES] | ||
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 | |||
4 | body { | ||
5 | margin: 0; | ||
6 | background-color: #FFFFFF; | ||
7 | padding: 0; | ||
8 | font-size: 80%; | ||
9 | font-family: verdana, sans-serif; | ||
10 | color: #000000; | ||
11 | } | ||
12 | |||
13 | table { | ||
14 | /* this is a trick to force tables to inherit the body font size */ | ||
15 | font-size: 100%; | ||
16 | } | ||
17 | |||
18 | /* non-scrolling (control) region style */ | ||
19 | |||
20 | div#control { | ||
21 | margin: 0; | ||
22 | background-color: #D4DFFF; | ||
23 | padding: 4px; | ||
24 | width: 100%; | ||
25 | border-bottom-color: #C8CDDE; | ||
26 | border-bottom-style: solid; | ||
27 | border-bottom-width: 1; | ||
28 | } | ||
29 | |||
30 | span.productTitle { | ||
31 | font-size: 80%; | ||
32 | } | ||
33 | |||
34 | span.topicTitle { | ||
35 | font-size: 140%; | ||
36 | font-weight: bold; | ||
37 | color: #003399; | ||
38 | } | ||
39 | |||
40 | span#chickenFeet { | ||
41 | float: left; | ||
42 | } | ||
43 | |||
44 | span#languageFilter { | ||
45 | float: right; | ||
46 | } | ||
47 | |||
48 | /* scrolling (content) region style */ | ||
49 | |||
50 | div#main { | ||
51 | margin: 0; | ||
52 | padding: 1em; | ||
53 | width: 100%; | ||
54 | clear: both; | ||
55 | } | ||
56 | |||
57 | /* sections */ | ||
58 | |||
59 | div#header { | ||
60 | font-size: 70%; | ||
61 | color: #666666; | ||
62 | margin-bottom: 0.5em; | ||
63 | } | ||
64 | |||
65 | div.section { | ||
66 | margin-bottom: 1em; | ||
67 | } | ||
68 | |||
69 | div.sectionTitle { | ||
70 | display: inline; | ||
71 | font-size: 120%; | ||
72 | font-weight: bold; | ||
73 | color: #003399; | ||
74 | } | ||
75 | |||
76 | div.sectionContent { | ||
77 | margin-top: 0.2em; | ||
78 | } | ||
79 | |||
80 | span.subsectionTitle { | ||
81 | font-weight: bold; | ||
82 | } | ||
83 | |||
84 | div#footer { | ||
85 | margin-top: 1em; | ||
86 | border-top: thin solid #003399; | ||
87 | padding-top: 0.5em; | ||
88 | } | ||
89 | |||
90 | /* authored content (block) */ | ||
91 | |||
92 | p { | ||
93 | margin-top: 0; | ||
94 | margin-bottom: 1em; | ||
95 | } | ||
96 | |||
97 | dl { | ||
98 | margin-top: 0; | ||
99 | margin-bottom: 1em; | ||
100 | } | ||
101 | |||
102 | div.code { | ||
103 | clear: both; | ||
104 | width: 100%; | ||
105 | background: #F7F7FF; | ||
106 | padding: 0.4em; | ||
107 | font-family: "Andale Mono"; | ||
108 | /* font-family: "Courier New"; */ | ||
109 | /* font-family: "This is not a monospace font", monospace; */ | ||
110 | font-size: inherit; | ||
111 | margin-bottom: 1em; | ||
112 | } | ||
113 | |||
114 | pre { | ||
115 | margin: 0; | ||
116 | padding: 0; | ||
117 | } | ||
118 | |||
119 | table.authoredTable { | ||
120 | table-layout: fixed; | ||
121 | width: 100%; | ||
122 | margin-bottom: 1em; | ||
123 | } | ||
124 | |||
125 | table.authoredTable th { | ||
126 | border-bottom-color: #C8CDDE; | ||
127 | border-bottom-style: solid; | ||
128 | border-bottom-width: 1; | ||
129 | background: #EFEFF7; | ||
130 | padding: 0.2em; | ||
131 | text-align: left; | ||
132 | color: #000066; | ||
133 | font-weight: bold; | ||
134 | } | ||
135 | |||
136 | table.authoredTable td { | ||
137 | border-bottom-style: solid; | ||
138 | border-bottom-color: #C8CDDE; | ||
139 | border-bottom-width: 1px; | ||
140 | background: #F7F7FF; | ||
141 | padding: 0.2em; | ||
142 | vertical-align: top; | ||
143 | } | ||
144 | |||
145 | div.alert { | ||
146 | border: 1px solid #C8CDDE; | ||
147 | background: #F7F7FF; | ||
148 | } | ||
149 | |||
150 | div.media { | ||
151 | text-align: center; | ||
152 | margin-bottom: 1em; | ||
153 | } | ||
154 | |||
155 | |||
156 | /* authored content (inline) */ | ||
157 | |||
158 | span.keyword { | ||
159 | font-weight: bold; | ||
160 | } | ||
161 | |||
162 | span.code { | ||
163 | font-family: "Andale Mono", "Courier New", Courier, monospace; | ||
164 | } | ||
165 | |||
166 | /* auto-generated controls */ | ||
167 | |||
168 | div.langTabs { | ||
169 | width: 100%; | ||
170 | } | ||
171 | |||
172 | div.langTab { | ||
173 | float: left; | ||
174 | width: 16%; | ||
175 | border-top: 1px solid #C8CDDE; | ||
176 | border-left: 1px solid #C8CDDE; | ||
177 | border-right: 1px solid #C8CDDE; | ||
178 | background: #F7F7FF; | ||
179 | padding: 0.2em; | ||
180 | text-align: left; | ||
181 | color: #000066; | ||
182 | font-weight: normal; | ||
183 | } | ||
184 | |||
185 | div.activeLangTab { | ||
186 | float: left; | ||
187 | width: 16%; | ||
188 | border-top: 1px solid #C8CDDE; | ||
189 | border-left: 1px solid #C8CDDE; | ||
190 | border-right: 1px solid #C8CDDE; | ||
191 | background: #EFEFF7; | ||
192 | padding: 0.2em; | ||
193 | text-align: left; | ||
194 | color: #000066; | ||
195 | font-weight: bold; | ||
196 | } | ||
197 | |||
198 | table.members { | ||
199 | table-layout: fixed; | ||
200 | width: 100%; | ||
201 | } | ||
202 | |||
203 | table.members th.iconColumn { | ||
204 | width: 60px; | ||
205 | } | ||
206 | |||
207 | table.members th.nameColumn { | ||
208 | width: 33%; | ||
209 | } | ||
210 | |||
211 | table.members th.descriptionColumn { | ||
212 | width: 66%; | ||
213 | } | ||
214 | |||
215 | table.members th { | ||
216 | border-bottom-color: #C8CDDE; | ||
217 | border-bottom-style: solid; | ||
218 | border-bottom-width: 1; | ||
219 | background: #EFEFF7; | ||
220 | padding: 0.2em; | ||
221 | text-align: left; | ||
222 | color: #000066; | ||
223 | font-weight: bold; | ||
224 | } | ||
225 | |||
226 | table.members td { | ||
227 | border-bottom-style: solid; | ||
228 | border-bottom-color: #C8CDDE; | ||
229 | border-bottom-width: 1px; | ||
230 | background: #F7F7FF; | ||
231 | padding: 0.2em; | ||
232 | vertical-align: top; | ||
233 | overflow: hidden; | ||
234 | } | ||
235 | |||
236 | table.exceptions { | ||
237 | table-layout: fixed; | ||
238 | width: 100%; | ||
239 | } | ||
240 | |||
241 | |||
242 | table.exceptions th.exceptionNameColumn { | ||
243 | width: 33%; | ||
244 | } | ||
245 | |||
246 | table.exceptions th.exceptionConditionColumn { | ||
247 | width: 66%; | ||
248 | } | ||
249 | |||
250 | table.exceptions th { | ||
251 | border-bottom-color: #C8CDDE; | ||
252 | border-bottom-style: solid; | ||
253 | border-bottom-width: 1; | ||
254 | background: #EFEFF7; | ||
255 | padding: 0.2em; | ||
256 | text-align: left; | ||
257 | color: #000066; | ||
258 | font-weight: bold; | ||
259 | } | ||
260 | |||
261 | table.exceptions td { | ||
262 | border-bottom-style: solid; | ||
263 | border-bottom-color: #C8CDDE; | ||
264 | border-bottom-width: 1px; | ||
265 | background: #F7F7FF; | ||
266 | padding: 0.2em; | ||
267 | vertical-align: top; | ||
268 | } | ||
269 | |||
270 | table.permissions { | ||
271 | table-layout: fixed; | ||
272 | width: 100%; | ||
273 | } | ||
274 | |||
275 | |||
276 | table.permissions th.permissionNameColumn { | ||
277 | width: 33%; | ||
278 | } | ||
279 | |||
280 | table.permissions th.permissionConditionColumn { | ||
281 | width: 66%; | ||
282 | } | ||
283 | |||
284 | table.permissions th { | ||
285 | border-bottom-color: #C8CDDE; | ||
286 | border-bottom-style: solid; | ||
287 | border-bottom-width: 1; | ||
288 | background: #EFEFF7; | ||
289 | padding: 0.2em; | ||
290 | text-align: left; | ||
291 | color: #000066; | ||
292 | font-weight: bold; | ||
293 | } | ||
294 | |||
295 | table.permissions td { | ||
296 | border-bottom-style: solid; | ||
297 | border-bottom-color: #C8CDDE; | ||
298 | border-bottom-width: 1px; | ||
299 | background: #F7F7FF; | ||
300 | padding: 0.2em; | ||
301 | vertical-align: top; | ||
302 | } | ||
303 | |||
304 | span.obsolete { | ||
305 | color: red; | ||
306 | } | ||
307 | |||
308 | span.cs { | ||
309 | display: inline; | ||
310 | } | ||
311 | |||
312 | span.vb { | ||
313 | display: none; | ||
314 | } | ||
315 | |||
316 | span.cpp { | ||
317 | display: none; | ||
318 | } | ||
319 | /* syntax styling */ | ||
320 | |||
321 | div.code span.identifier { | ||
322 | font-size: 120%; | ||
323 | font-weight: bold; | ||
324 | } | ||
325 | |||
326 | div.code span.keyword { | ||
327 | color: green; | ||
328 | } | ||
329 | |||
330 | div.code span.parameter { | ||
331 | font-style: italic; | ||
332 | color: purple; | ||
333 | } | ||
334 | |||
335 | div.code span.literal { | ||
336 | color: purple; | ||
337 | } | ||
338 | |||
339 | div.code span.comment { | ||
340 | color: red; | ||
341 | } | ||
342 | |||
343 | span.foreignPhrase { | ||
344 | font-style: italic; | ||
345 | } | ||
346 | |||
347 | span.placeholder { | ||
348 | font-style: italic; | ||
349 | } | ||
350 | |||
351 | a { | ||
352 | color: blue; | ||
353 | font-weight: bold; | ||
354 | text-decoration: none; | ||
355 | } | ||
356 | |||
357 | MSHelp\:link { | ||
358 | color: blue; | ||
359 | font-weight: bold; | ||
360 | hoverColor: #3366ff; | ||
361 | } | ||
362 | |||
363 | span.nolink { | ||
364 | font-weight: bold; | ||
365 | } | ||
366 | |||
367 | table.filter { | ||
368 | table-layout: fixed; | ||
369 | } | ||
370 | |||
371 | tr.tabs td.tab { | ||
372 | width: 10em; | ||
373 | background: #F7F7FF; | ||
374 | padding: 0.2em; | ||
375 | text-align: left; | ||
376 | color: #000066; | ||
377 | font-weight: normal; | ||
378 | overflow: hidden; | ||
379 | cursor: pointer; | ||
380 | } | ||
381 | |||
382 | tr.tabs td.activeTab { | ||
383 | width: 10em; | ||
384 | background: #EFEFF7; | ||
385 | padding: 0.2em; | ||
386 | text-align: left; | ||
387 | color: #000066; | ||
388 | font-weight: bold; | ||
389 | overflow: hidden; | ||
390 | } | ||
391 | |||
392 | td.line { | ||
393 | background: #EFEFF7; | ||
394 | } | ||
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>&lt%3bscript src=&quot%3bhelplink.js&quot%3b&gt%3b&lt%3b/script&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 | |||
3 | FixHelpLinks(); | ||
4 | |||
5 | function GetHelpCode(apiName) | ||
6 | { | ||
7 | switch (apiName.toLowerCase()) | ||
8 | { | ||
9 | case "msiadvertiseproduct": return 370056; | ||
10 | case "msiadvertiseproductex": return 370057; | ||
11 | case "msiapplymultiplepatches": return 370059; | ||
12 | case "msiapplypatch": return 370060; | ||
13 | case "msibegintransaction": return 736312; | ||
14 | case "msiclosehandle": return 370067; | ||
15 | case "msicollectuserinfo": return 370068; | ||
16 | case "msiconfigurefeature": return 370069; | ||
17 | case "msiconfigureproduct": return 370070; | ||
18 | case "msiconfigureproductex": return 370071; | ||
19 | case "msicreaterecord": return 370072; | ||
20 | case "msicreatetransformsummaryinfo": return 370073; | ||
21 | case "msidatabaseapplytransform": return 370074; | ||
22 | case "msidatabasecommit": return 370075; | ||
23 | case "msidatabaseexport": return 370076; | ||
24 | case "msidatabasegeneratetransform": return 370077; | ||
25 | case "msidatabasegetprimarykeys": return 370078; | ||
26 | case "msidatabaseimport": return 370079; | ||
27 | case "msidatabaseistablepersistent": return 370080; | ||
28 | case "msidatabasemerge": return 370081; | ||
29 | case "msidatabaseopenview": return 370082; | ||
30 | case "msidetermineapplicablepatches": return 370084; | ||
31 | case "msideterminepatchsequence": return 370085; | ||
32 | case "msidoaction": return 370090; | ||
33 | case "msienablelog": return 370091; | ||
34 | case "msiendtransaction": return 736318; | ||
35 | case "msienumclients": return 370094; | ||
36 | case "msienumcomponentcosts": return 370095; | ||
37 | case "msienumcomponentqualifiers": return 370096; | ||
38 | case "msienumcomponents": return 370097; | ||
39 | case "msienumfeatures": return 370098; | ||
40 | case "msienumpatches": return 370099; | ||
41 | case "msienumpatchesex": return 370100; | ||
42 | case "msienumproducts": return 370101; | ||
43 | case "msienumproductsex": return 370102; | ||
44 | case "msienumrelatedproducts": return 370103; | ||
45 | case "msievaluatecondition": return 370104; | ||
46 | case "msiextractpatchxmldata": return 370105; | ||
47 | case "msiformatrecord": return 370109; | ||
48 | case "msigetactivedatabase": return 370110; | ||
49 | case "msigetcomponentpath": return 370112; | ||
50 | case "msigetcomponentstate": return 370113; | ||
51 | case "msigetdatabasestate": return 370114; | ||
52 | case "msigetfeaturecost": return 370115; | ||
53 | case "msigetfeatureinfo": return 370116; | ||
54 | case "msigetfeaturestate": return 370117; | ||
55 | case "msigetfeatureusage": return 370118; | ||
56 | case "msigetfeaturevalidstates": return 370119; | ||
57 | case "msigetfilehash": return 370120; | ||
58 | case "msigetfileversion": return 370122; | ||
59 | case "msigetlanguage": return 370123; | ||
60 | case "msigetlasterrorrecord": return 370124; | ||
61 | case "msigetmode": return 370125; | ||
62 | case "msigetpatchfilelist": return 370126; | ||
63 | case "msigetpatchinfo": return 370127; | ||
64 | case "msigetpatchinfoex": return 370128; | ||
65 | case "msigetproductcode": return 370129; | ||
66 | case "msigetproductinfo": return 370130; | ||
67 | case "msigetproductinfoex": return 370131; | ||
68 | case "msigetproductinfofromscript": return 370132; | ||
69 | case "msigetproductproperty": return 370133; | ||
70 | case "msigetproperty": return 370134; | ||
71 | case "msigetshortcuttarget": return 370299; | ||
72 | case "msigetsourcepath": return 370300; | ||
73 | case "msigetsummaryinformation": return 370301; | ||
74 | case "msigettargetpath": return 370303; | ||
75 | case "msiinstallmissingcomponent": return 370311; | ||
76 | case "msiinstallmissingfile": return 370313; | ||
77 | case "msiinstallproduct": return 370315; | ||
78 | case "msijointransaction": return 736319; | ||
79 | case "msilocatecomponent": return 370320; | ||
80 | case "msinotifysidchange": return 370328; | ||
81 | case "msiopendatabase": return 370338; | ||
82 | case "msiopenpackage": return 370339; | ||
83 | case "msiopenpackageex": return 370340; | ||
84 | case "msiopenproduct": return 370341; | ||
85 | case "msiprocessadvertisescript": return 370353; | ||
86 | case "msiprocessmessage": return 370354; | ||
87 | case "msiprovideassembly": return 370355; | ||
88 | case "msiprovidecomponent": return 370356; | ||
89 | case "msiprovidequalifiedcomponent": return 370357; | ||
90 | case "msiprovidequalifiedcomponentex":return 370358; | ||
91 | case "msiquerycomponnetstate": return 370360; | ||
92 | case "msiqueryfeaturestate": return 370361; | ||
93 | case "msiqueryfeaturestateex": return 370362; | ||
94 | case "msiqueryproductstate": return 370363; | ||
95 | case "msirecordcleardata": return 370364; | ||
96 | case "msirecorddatasize": return 370365; | ||
97 | case "msirecordgetfieldcount": return 370366; | ||
98 | case "msirecordgetinteger": return 370367; | ||
99 | case "msirecordgetstring": return 370368; | ||
100 | case "msirecordisnull": return 370369; | ||
101 | case "msirecordreadstream": return 370370; | ||
102 | case "msirecordsetinteger": return 370371; | ||
103 | case "msirecordsetstream": return 370372; | ||
104 | case "msirecordsetstring": return 370373; | ||
105 | case "msireinstallfeature": return 370374; | ||
106 | case "msireinstallproduct": return 370375; | ||
107 | case "msiremovepatches": return 370376; | ||
108 | case "msisequence": return 370382; | ||
109 | case "msisetcomponentstate": return 370383; | ||
110 | case "msisetexternalui": return 370384; | ||
111 | case "msisetexternaluirecord": return 370385; | ||
112 | case "msisetfeatureattributes": return 370386; | ||
113 | case "msisetfeaturestate": return 370387; | ||
114 | case "msisetinstalllevel": return 370388; | ||
115 | case "msisetinternalui": return 370389; | ||
116 | case "msisetmode": return 370390; | ||
117 | case "msisetproperty": return 370391; | ||
118 | case "msisettargetpath": return 370392; | ||
119 | case "msisourcelistaddmediadisk": return 370394; | ||
120 | case "msisourcelistaddsource": return 370395; | ||
121 | case "msisourcelistaddsourceex": return 370396; | ||
122 | case "msisourcelistclearall": return 370397; | ||
123 | case "msisourcelistclearallex": return 370398; | ||
124 | case "msisourcelistclearmediadisk": return 370399; | ||
125 | case "msisourcelistclearsource": return 370401; | ||
126 | case "msisourcelistenummediadisks": return 370402; | ||
127 | case "msisourcelistenumsources": return 370403; | ||
128 | case "msisourcelistforceresolution": return 370404; | ||
129 | case "msisourcelistforceresolutionex":return 370405; | ||
130 | case "msisourcelistgetinfo": return 370406; | ||
131 | case "msisourcelistsetinfo": return 370407; | ||
132 | case "msisummaryinfogetproperty": return 370409; | ||
133 | case "msisummaryinfopersist": return 370490; | ||
134 | case "msisummaryinfosetproperty": return 370491; | ||
135 | case "msiusefeature": return 370502; | ||
136 | case "msiusefeatureex": return 370503; | ||
137 | case "msiverifydiskspace": return 370506; | ||
138 | case "msiverifypackage": return 370508; | ||
139 | case "msiviewexecute": return 370513; | ||
140 | case "msiviewfetch": return 370514; | ||
141 | case "msiviewgetcolumninfo": return 370516; | ||
142 | case "msiviewgeterror": return 370518; | ||
143 | case "msiviewmodify": return 370519; | ||
144 | case "productid": return 370855; | ||
145 | default: | ||
146 | return 0; | ||
147 | } | ||
148 | } | ||
149 | |||
150 | function GetHelpLink(apiName) | ||
151 | { | ||
152 | var helpCode = GetHelpCode(apiName); | ||
153 | if (helpCode != 0) | ||
154 | { | ||
155 | // Found a direct link! | ||
156 | var prefix = (helpCode < 500000 ? "aa" : "bb"); | ||
157 | return "http://msdn2.microsoft.com/en-us/library/" + prefix + helpCode + ".aspx"; | ||
158 | } | ||
159 | else | ||
160 | { | ||
161 | // This link works, but goes through an annoying 5-sec redirect page. | ||
162 | return "http://msdn.microsoft.com/library/en-us/msi/setup/" + apiName.toLowerCase() + ".asp"; | ||
163 | } | ||
164 | } | ||
165 | |||
166 | // Change any MSI API help links from indirect MSDN references to direct references. | ||
167 | function FixHelpLinks() | ||
168 | { | ||
169 | var msiLinkRegex = /msdn\.microsoft\.com\/library\/en-us\/msi\/setup\/([a-z]+)\.asp/i; | ||
170 | var links = document.body.all.tags("a"); | ||
171 | var i; | ||
172 | for (i = 0; i < links.length; i++) | ||
173 | { | ||
174 | var linkElem = links(i); | ||
175 | var match = msiLinkRegex.exec(linkElem.href); | ||
176 | if (match) | ||
177 | { | ||
178 | var apiName = match[1]; | ||
179 | linkElem.href = GetHelpLink(apiName); | ||
180 | linkElem.target = "_blank"; | ||
181 | linkElem.title = "MSDN Library"; | ||
182 | } | ||
183 | } | ||
184 | } | ||
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 | |||
3 | using 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 | |||
3 | namespace WixToolset.Dtf.Samples.EmbeddedUI | ||
4 | { | ||
5 | using System; | ||
6 | using WixToolset.Dtf.WindowsInstaller; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Tracks MSI progress messages and converts them to usable progress. | ||
10 | /// </summary> | ||
11 | public class InstallProgressCounter | ||
12 | { | ||
13 | private int total; | ||
14 | private int completed; | ||
15 | private int step; | ||
16 | private bool moveForward; | ||
17 | private bool enableActionData; | ||
18 | private int progressPhase; | ||
19 | private double scriptPhaseWeight; | ||
20 | |||
21 | public InstallProgressCounter() : this(0.3) | ||
22 | { | ||
23 | } | ||
24 | |||
25 | public InstallProgressCounter(double scriptPhaseWeight) | ||
26 | { | ||
27 | if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1)) | ||
28 | { | ||
29 | throw new ArgumentOutOfRangeException("scriptPhaseWeight"); | ||
30 | } | ||
31 | |||
32 | this.scriptPhaseWeight = scriptPhaseWeight; | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Gets a number between 0 and 1 that indicates the overall installation progress. | ||
37 | /// </summary> | ||
38 | public double Progress { get; private set; } | ||
39 | |||
40 | public void ProcessMessage(InstallMessage messageType, Record messageRecord) | ||
41 | { | ||
42 | // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. | ||
43 | |||
44 | switch (messageType) | ||
45 | { | ||
46 | case InstallMessage.ActionStart: | ||
47 | if (this.enableActionData) | ||
48 | { | ||
49 | this.enableActionData = false; | ||
50 | } | ||
51 | break; | ||
52 | |||
53 | case InstallMessage.ActionData: | ||
54 | if (this.enableActionData) | ||
55 | { | ||
56 | if (this.moveForward) | ||
57 | { | ||
58 | this.completed += this.step; | ||
59 | } | ||
60 | else | ||
61 | { | ||
62 | this.completed -= this.step; | ||
63 | } | ||
64 | |||
65 | this.UpdateProgress(); | ||
66 | } | ||
67 | break; | ||
68 | |||
69 | case InstallMessage.Progress: | ||
70 | this.ProcessProgressMessage(messageRecord); | ||
71 | break; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | private void ProcessProgressMessage(Record progressRecord) | ||
76 | { | ||
77 | // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#. | ||
78 | |||
79 | if (progressRecord == null || progressRecord.FieldCount == 0) | ||
80 | { | ||
81 | return; | ||
82 | } | ||
83 | |||
84 | int fieldCount = progressRecord.FieldCount; | ||
85 | int progressType = progressRecord.GetInteger(1); | ||
86 | string progressTypeString = String.Empty; | ||
87 | switch (progressType) | ||
88 | { | ||
89 | case 0: // Master progress reset | ||
90 | if (fieldCount < 4) | ||
91 | { | ||
92 | return; | ||
93 | } | ||
94 | |||
95 | this.progressPhase++; | ||
96 | |||
97 | this.total = progressRecord.GetInteger(2); | ||
98 | if (this.progressPhase == 1) | ||
99 | { | ||
100 | // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase | ||
101 | // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress | ||
102 | // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this | ||
103 | // "close" and deal with the rest. | ||
104 | this.total += 50; | ||
105 | } | ||
106 | |||
107 | this.moveForward = (progressRecord.GetInteger(3) == 0); | ||
108 | this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max | ||
109 | this.enableActionData = false; | ||
110 | |||
111 | this.UpdateProgress(); | ||
112 | break; | ||
113 | |||
114 | case 1: // Action info | ||
115 | if (fieldCount < 3) | ||
116 | { | ||
117 | return; | ||
118 | } | ||
119 | |||
120 | if (progressRecord.GetInteger(3) == 0) | ||
121 | { | ||
122 | this.enableActionData = false; | ||
123 | } | ||
124 | else | ||
125 | { | ||
126 | this.enableActionData = true; | ||
127 | this.step = progressRecord.GetInteger(2); | ||
128 | } | ||
129 | break; | ||
130 | |||
131 | case 2: // Progress report | ||
132 | if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0) | ||
133 | { | ||
134 | return; | ||
135 | } | ||
136 | |||
137 | if (this.moveForward) | ||
138 | { | ||
139 | this.completed += progressRecord.GetInteger(2); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | this.completed -= progressRecord.GetInteger(2); | ||
144 | } | ||
145 | |||
146 | this.UpdateProgress(); | ||
147 | break; | ||
148 | |||
149 | case 3: // Progress total addition | ||
150 | this.total += progressRecord.GetInteger(2); | ||
151 | break; | ||
152 | } | ||
153 | } | ||
154 | |||
155 | private void UpdateProgress() | ||
156 | { | ||
157 | if (this.progressPhase < 1 || this.total == 0) | ||
158 | { | ||
159 | this.Progress = 0; | ||
160 | } | ||
161 | else if (this.progressPhase == 1) | ||
162 | { | ||
163 | this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total; | ||
164 | } | ||
165 | else if (this.progressPhase == 2) | ||
166 | { | ||
167 | this.Progress = this.scriptPhaseWeight + | ||
168 | (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total; | ||
169 | } | ||
170 | else | ||
171 | { | ||
172 | this.Progress = 1; | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | } | ||
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 | |||
3 | namespace WixToolset.Dtf.Samples.EmbeddedUI | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Configuration; | ||
8 | using System.Threading; | ||
9 | using System.Windows; | ||
10 | using System.Windows.Threading; | ||
11 | using WixToolset.Dtf.WindowsInstaller; | ||
12 | using Application = System.Windows.Application; | ||
13 | |||
14 | public class SampleEmbeddedUI : IEmbeddedUI | ||
15 | { | ||
16 | private Thread appThread; | ||
17 | private Application app; | ||
18 | private SetupWizard setupWizard; | ||
19 | private ManualResetEvent installStartEvent; | ||
20 | private ManualResetEvent installExitEvent; | ||
21 | |||
22 | /// <summary> | ||
23 | /// Initializes the embedded UI. | ||
24 | /// </summary> | ||
25 | /// <param name="session">Handle to the installer which can be used to get and set properties. | ||
26 | /// The handle is only valid for the duration of this method call.</param> | ||
27 | /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param> | ||
28 | /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this | ||
29 | /// method returns, the installer resets the UI level to the returned value of this parameter.</param> | ||
30 | /// <returns>True if the embedded UI was successfully initialized; false if the installation | ||
31 | /// should continue without the embedded UI.</returns> | ||
32 | /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception> | ||
33 | /// <exception cref="InstallerException">The embedded UI failed to initialize and | ||
34 | /// causes the installation to fail.</exception> | ||
35 | public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel) | ||
36 | { | ||
37 | if (session != null) | ||
38 | { | ||
39 | if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full) | ||
40 | { | ||
41 | // Don't show custom UI when the UI level is set to basic. | ||
42 | return false; | ||
43 | |||
44 | // An embedded UI could display an alternate dialog sequence for reduced or | ||
45 | // basic modes, but it's not implemented here. We'll just fall back to the | ||
46 | // built-in MSI basic UI. | ||
47 | } | ||
48 | |||
49 | if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase)) | ||
50 | { | ||
51 | // Don't show custom UI when uninstalling. | ||
52 | return false; | ||
53 | |||
54 | // An embedded UI could display an uninstall wizard, it's just not imlemented here. | ||
55 | } | ||
56 | } | ||
57 | |||
58 | // Start the setup wizard on a separate thread. | ||
59 | this.installStartEvent = new ManualResetEvent(false); | ||
60 | this.installExitEvent = new ManualResetEvent(false); | ||
61 | this.appThread = new Thread(this.Run); | ||
62 | this.appThread.SetApartmentState(ApartmentState.STA); | ||
63 | this.appThread.Start(); | ||
64 | |||
65 | // Wait for the setup wizard to either kickoff the install or prematurely exit. | ||
66 | int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent }); | ||
67 | if (waitResult == 1) | ||
68 | { | ||
69 | // The setup wizard set the exit event instead of the start event. Cancel the installation. | ||
70 | throw new InstallCanceledException(); | ||
71 | } | ||
72 | else | ||
73 | { | ||
74 | // Start the installation with a silenced internal UI. | ||
75 | // This "embedded external UI" will handle message types except for source resolution. | ||
76 | internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly; | ||
77 | return true; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Processes information and progress messages sent to the user interface. | ||
83 | /// </summary> | ||
84 | /// <param name="messageType">Message type.</param> | ||
85 | /// <param name="messageRecord">Record that contains message data.</param> | ||
86 | /// <param name="buttons">Message box buttons.</param> | ||
87 | /// <param name="icon">Message box icon.</param> | ||
88 | /// <param name="defaultButton">Message box default button.</param> | ||
89 | /// <returns>Result of processing the message.</returns> | ||
90 | public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, | ||
91 | MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) | ||
92 | { | ||
93 | // Synchronously send the message to the setup wizard window on its thread. | ||
94 | object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send, | ||
95 | new Func<MessageResult>(delegate() | ||
96 | { | ||
97 | return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton); | ||
98 | })); | ||
99 | return (MessageResult) result; | ||
100 | } | ||
101 | |||
102 | /// <summary> | ||
103 | /// Shuts down the embedded UI at the end of the installation. | ||
104 | /// </summary> | ||
105 | /// <remarks> | ||
106 | /// If the installation was canceled during initialization, this method will not be called. | ||
107 | /// If the installation was canceled or failed at any later point, this method will be called at the end. | ||
108 | /// </remarks> | ||
109 | public void Shutdown() | ||
110 | { | ||
111 | // Wait for the user to exit the setup wizard. | ||
112 | this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal, | ||
113 | new Action(delegate() | ||
114 | { | ||
115 | this.setupWizard.EnableExit(); | ||
116 | })); | ||
117 | this.appThread.Join(); | ||
118 | } | ||
119 | |||
120 | /// <summary> | ||
121 | /// Creates the setup wizard and runs the application thread. | ||
122 | /// </summary> | ||
123 | private void Run() | ||
124 | { | ||
125 | this.app = new Application(); | ||
126 | this.setupWizard = new SetupWizard(this.installStartEvent); | ||
127 | this.setupWizard.InitializeComponent(); | ||
128 | this.app.Run(this.setupWizard); | ||
129 | this.installExitEvent.Set(); | ||
130 | } | ||
131 | } | ||
132 | } | ||
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 | |||
3 | namespace WixToolset.Dtf.Samples.EmbeddedUI | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Linq; | ||
8 | using System.Text; | ||
9 | using System.Threading; | ||
10 | using System.Windows; | ||
11 | using System.Windows.Controls; | ||
12 | using System.Windows.Data; | ||
13 | using System.Windows.Documents; | ||
14 | using System.Windows.Input; | ||
15 | using System.Windows.Media; | ||
16 | using System.Windows.Media.Imaging; | ||
17 | using System.Windows.Navigation; | ||
18 | using System.Windows.Shapes; | ||
19 | using WixToolset.Dtf.WindowsInstaller; | ||
20 | |||
21 | /// <summary> | ||
22 | /// Interaction logic for SetupWizard.xaml | ||
23 | /// </summary> | ||
24 | public partial class SetupWizard : Window | ||
25 | { | ||
26 | private ManualResetEvent installStartEvent; | ||
27 | private InstallProgressCounter progressCounter; | ||
28 | private bool canceled; | ||
29 | |||
30 | public SetupWizard(ManualResetEvent installStartEvent) | ||
31 | { | ||
32 | this.installStartEvent = installStartEvent; | ||
33 | this.progressCounter = new InstallProgressCounter(0.5); | ||
34 | } | ||
35 | |||
36 | public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord, | ||
37 | MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) | ||
38 | { | ||
39 | try | ||
40 | { | ||
41 | this.progressCounter.ProcessMessage(messageType, messageRecord); | ||
42 | this.progressBar.Value = this.progressBar.Minimum + | ||
43 | this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum); | ||
44 | this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%"; | ||
45 | |||
46 | switch (messageType) | ||
47 | { | ||
48 | case InstallMessage.Error: | ||
49 | case InstallMessage.Warning: | ||
50 | case InstallMessage.Info: | ||
51 | string message = String.Format("{0}: {1}", messageType, messageRecord); | ||
52 | this.LogMessage(message); | ||
53 | break; | ||
54 | } | ||
55 | |||
56 | if (this.canceled) | ||
57 | { | ||
58 | this.canceled = false; | ||
59 | return MessageResult.Cancel; | ||
60 | } | ||
61 | } | ||
62 | catch (Exception ex) | ||
63 | { | ||
64 | this.LogMessage(ex.ToString()); | ||
65 | this.LogMessage(ex.StackTrace); | ||
66 | } | ||
67 | |||
68 | return MessageResult.OK; | ||
69 | } | ||
70 | |||
71 | private void LogMessage(string message) | ||
72 | { | ||
73 | this.messagesTextBox.Text += Environment.NewLine + message; | ||
74 | this.messagesTextBox.ScrollToEnd(); | ||
75 | } | ||
76 | |||
77 | internal void EnableExit() | ||
78 | { | ||
79 | this.progressBar.Visibility = Visibility.Hidden; | ||
80 | this.progressLabel.Visibility = Visibility.Hidden; | ||
81 | this.cancelButton.Visibility = Visibility.Hidden; | ||
82 | this.exitButton.Visibility = Visibility.Visible; | ||
83 | } | ||
84 | |||
85 | private void installButton_Click(object sender, RoutedEventArgs e) | ||
86 | { | ||
87 | this.installButton.Visibility = Visibility.Hidden; | ||
88 | this.progressBar.Visibility = Visibility.Visible; | ||
89 | this.progressLabel.Visibility = Visibility.Visible; | ||
90 | this.installStartEvent.Set(); | ||
91 | } | ||
92 | |||
93 | private void exitButton_Click(object sender, RoutedEventArgs e) | ||
94 | { | ||
95 | this.Close(); | ||
96 | } | ||
97 | |||
98 | private void cancelButton_Click(object sender, RoutedEventArgs e) | ||
99 | { | ||
100 | if (this.installButton.Visibility == Visibility.Visible) | ||
101 | { | ||
102 | this.Close(); | ||
103 | } | ||
104 | else | ||
105 | { | ||
106 | this.canceled = true; | ||
107 | this.cancelButton.IsEnabled = false; | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Data; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Windows.Forms; | ||
10 | using WixToolset.Dtf.WindowsInstaller; | ||
11 | |||
12 | namespace WixToolset.Dtf.Samples.Inventory | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Provides inventory data about features of products installed on the system. | ||
16 | /// </summary> | ||
17 | public class FeaturesInventory : IInventoryDataProvider | ||
18 | { | ||
19 | private static object syncRoot = new object(); | ||
20 | |||
21 | public FeaturesInventory() | ||
22 | { | ||
23 | } | ||
24 | |||
25 | public string Description | ||
26 | { | ||
27 | get { return "Features of installed products"; } | ||
28 | } | ||
29 | |||
30 | public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback) | ||
31 | { | ||
32 | statusCallback(0, @"Products\...\Features"); | ||
33 | ArrayList nodes = new ArrayList(); | ||
34 | foreach (ProductInstallation product in ProductInstallation.AllProducts) | ||
35 | { | ||
36 | nodes.Add(String.Format(@"Products\{0}\Features", MsiUtils.GetProductName(product.ProductCode))); | ||
37 | } | ||
38 | statusCallback(nodes.Count, String.Empty); | ||
39 | return (string[]) nodes.ToArray(typeof(string)); | ||
40 | } | ||
41 | |||
42 | public bool IsNodeSearchable(string searchRoot, string searchNode) | ||
43 | { | ||
44 | return true; | ||
45 | } | ||
46 | |||
47 | public DataView GetData(string nodePath) | ||
48 | { | ||
49 | string[] path = nodePath.Split('\\'); | ||
50 | |||
51 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Features") | ||
52 | { | ||
53 | return GetProductFeaturesData(MsiUtils.GetProductCode(path[1])); | ||
54 | } | ||
55 | return null; | ||
56 | } | ||
57 | |||
58 | public DataView GetProductFeaturesData(string productCode) | ||
59 | { | ||
60 | DataTable table = new DataTable("ProductFeatures"); | ||
61 | table.Locale = CultureInfo.InvariantCulture; | ||
62 | table.Columns.Add("ProductFeaturesFeatureTitle", typeof(string)); | ||
63 | table.Columns.Add("ProductFeaturesFeatureName", typeof(string)); | ||
64 | table.Columns.Add("ProductFeaturesInstallState", typeof(string)); | ||
65 | |||
66 | try | ||
67 | { | ||
68 | IntPtr hWnd = IntPtr.Zero; | ||
69 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
70 | lock(syncRoot) // Only one Installer session can be active at a time | ||
71 | { | ||
72 | using(Session session = Installer.OpenProduct(productCode)) | ||
73 | { | ||
74 | session.DoAction("CostInitialize"); | ||
75 | session.DoAction("FileCost"); | ||
76 | session.DoAction("CostFinalize"); | ||
77 | |||
78 | IList<string> featuresAndTitles = session.Database.ExecuteStringQuery( | ||
79 | "SELECT `Title`, `Feature` FROM `Feature`"); | ||
80 | |||
81 | for(int i = 0; i < featuresAndTitles.Count; i += 2) | ||
82 | { | ||
83 | InstallState featureState = session.Features[featuresAndTitles[i + 1]].CurrentState; | ||
84 | table.Rows.Add(new object[] { featuresAndTitles[i], featuresAndTitles[i+1], | ||
85 | (featureState == InstallState.Advertised ? "Advertised" : featureState.ToString()) }); | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | return new DataView(table, "", "ProductFeaturesFeatureTitle ASC", DataViewRowState.CurrentRows); | ||
90 | } | ||
91 | catch(InstallerException) { } | ||
92 | catch(IOException) { } | ||
93 | return null; | ||
94 | } | ||
95 | |||
96 | public string GetLink(string nodePath, DataRow row) | ||
97 | { | ||
98 | string[] path = nodePath.Split('\\'); | ||
99 | |||
100 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Features") | ||
101 | { | ||
102 | return String.Format(@"Products\{0}\Features\{1}", path[1], row["ProductFeaturesFeatureName"]); | ||
103 | } | ||
104 | return null; | ||
105 | } | ||
106 | } | ||
107 | } | ||
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 | |||
3 | using System; | ||
4 | using System.Data; | ||
5 | |||
6 | namespace WixToolset.Dtf.Samples.Inventory | ||
7 | { | ||
8 | /// <summary> | ||
9 | /// Reports the total number of items loaded so far by <see cref="IInventoryDataProvider.GetNodes"/>. | ||
10 | /// </summary> | ||
11 | public delegate void InventoryDataLoadStatusCallback(int itemsLoaded, string currentNode); | ||
12 | |||
13 | /// <summary> | ||
14 | /// Inventory data providers implement this interface to provide a particular type of data. | ||
15 | /// Implementors must provide a parameterless constructor. | ||
16 | /// </summary> | ||
17 | public interface IInventoryDataProvider | ||
18 | { | ||
19 | /// <summary> | ||
20 | /// Gets a description of the data provided. This description allows | ||
21 | /// the user to choose what type of data to gather. | ||
22 | /// </summary> | ||
23 | string Description { get; } | ||
24 | |||
25 | /// <summary> | ||
26 | /// Gets the paths of all nodes for which this object provides data. | ||
27 | /// </summary> | ||
28 | /// <param name="statusCallback">Callback for reporting status. | ||
29 | /// The callback should not necessarily be invoked for every individual | ||
30 | /// node loaded, rather only every significant chunk.</param> | ||
31 | /// <returns>An array of node paths. The parts of the node paths | ||
32 | /// are delimited by backslashes (\).</returns> | ||
33 | string[] GetNodes(InventoryDataLoadStatusCallback statusCallback); | ||
34 | |||
35 | /// <summary> | ||
36 | /// When related nodes of a tree consist of duplicate data, it's | ||
37 | /// inefficient to search them all. This method indicates which | ||
38 | /// nodes should be search and which should be ignored. | ||
39 | /// </summary> | ||
40 | /// <param name="searchRoot">Root node of the subtree-search.</param> | ||
41 | /// <param name="searchNode">Node which may or may not be searched.</param> | ||
42 | /// <returns>True if the node should be searched, false otherwise.</returns> | ||
43 | bool IsNodeSearchable(string searchRoot, string searchNode); | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets the data for a particular node. | ||
47 | /// </summary> | ||
48 | /// <param name="nodePath">Path of the node for which data is requested. | ||
49 | /// This is one of the paths returned by <see cref="GetNodes"/>.</param> | ||
50 | /// <returns>DataView of a table filled with data, or null if data is | ||
51 | /// not available.</returns> | ||
52 | DataView GetData(string nodePath); | ||
53 | |||
54 | /// <summary> | ||
55 | /// Gets the path of another node which provides more details about | ||
56 | /// a particular data row. | ||
57 | /// </summary> | ||
58 | /// <param name="nodePath">Path of the node containing the data | ||
59 | /// row being queried.</param> | ||
60 | /// <param name="row">Data row being queried.</param> | ||
61 | /// <returns>Path to another node. This is not necessarily | ||
62 | /// one of the nodes returned by <see cref="GetNodes"/>. If the | ||
63 | /// node path is unknown, it will be ignored. This method may | ||
64 | /// return null if there is no detail node for the row.</returns> | ||
65 | string GetLink(string nodePath, DataRow row); | ||
66 | } | ||
67 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Drawing; | ||
6 | using System.Collections; | ||
7 | using System.ComponentModel; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Windows.Forms; | ||
10 | using System.Globalization; | ||
11 | using System.Reflection; | ||
12 | using System.Resources; | ||
13 | using System.Threading; | ||
14 | using System.Security.Permissions; | ||
15 | using System.Data; | ||
16 | |||
17 | |||
18 | [assembly: AssemblyDescription("Shows a hierarchical, relational, searchable " + | ||
19 | " view of all of the product, feature, component, file, and patch data managed " + | ||
20 | "by MSI, for all products installed on the system.")] | ||
21 | |||
22 | [assembly: SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode=true)] | ||
23 | |||
24 | |||
25 | namespace WixToolset.Dtf.Samples.Inventory | ||
26 | { | ||
27 | public class Inventory : System.Windows.Forms.Form | ||
28 | { | ||
29 | [STAThread] | ||
30 | public static void Main() | ||
31 | { | ||
32 | if (WixToolset.Dtf.WindowsInstaller.Installer.Version < new Version(3, 0)) | ||
33 | { | ||
34 | MessageBox.Show("This application requires Windows Installer version 3.0 or later.", | ||
35 | "Inventory", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||
36 | return; | ||
37 | } | ||
38 | |||
39 | Application.Run(new Inventory()); | ||
40 | } | ||
41 | |||
42 | private IInventoryDataProvider[] dataProviders; | ||
43 | private Hashtable dataProviderMap; | ||
44 | private Hashtable data; | ||
45 | private ArrayList tablesLoading; | ||
46 | private bool searching; | ||
47 | private bool stopSearch; | ||
48 | private bool navigating; | ||
49 | private string continueSearchRoot; | ||
50 | private string continueSearchPath; | ||
51 | private DataGridCell continueSearchCell; | ||
52 | private DataGridCell continueSearchEndCell; | ||
53 | private bool mouseOverGridLink = false; | ||
54 | private Stack historyBack; | ||
55 | private Stack historyForward; | ||
56 | private Stack cellHistoryBack; | ||
57 | private Stack cellHistoryForward; | ||
58 | private static readonly DataGridCell anyCell = new DataGridCell(-1,-1); | ||
59 | private static readonly DataGridCell zeroCell = new DataGridCell(0,0); | ||
60 | private static object syncRoot = new object(); | ||
61 | |||
62 | private System.Windows.Forms.DataGrid dataGrid; | ||
63 | private System.Windows.Forms.TreeView treeView; | ||
64 | private System.Windows.Forms.Panel toolPanel; | ||
65 | private System.Windows.Forms.Splitter splitter; | ||
66 | private System.Windows.Forms.Panel dataPanel; | ||
67 | private System.Windows.Forms.Button backButton; | ||
68 | private System.Windows.Forms.Button forwardButton; | ||
69 | private System.Windows.Forms.Button findButton; | ||
70 | private System.Windows.Forms.TextBox findTextBox; | ||
71 | private System.Windows.Forms.Button refreshButton; | ||
72 | private System.Windows.Forms.Button findStopButton; | ||
73 | private System.Windows.Forms.CheckBox searchTreeCheckBox; | ||
74 | private System.Windows.Forms.ToolTip gridLinkTip; | ||
75 | private System.ComponentModel.IContainer components; | ||
76 | |||
77 | public Inventory() | ||
78 | { | ||
79 | InitializeComponent(); | ||
80 | |||
81 | this.gridLinkTip.InitialDelay = 0; | ||
82 | this.gridLinkTip.ReshowDelay = 0; | ||
83 | |||
84 | this.dataProviderMap = new Hashtable(); | ||
85 | this.data = new Hashtable(); | ||
86 | this.tablesLoading = new ArrayList(); | ||
87 | this.historyBack = new Stack(); | ||
88 | this.historyForward = new Stack(); | ||
89 | this.cellHistoryBack = new Stack(); | ||
90 | this.cellHistoryForward = new Stack(); | ||
91 | } | ||
92 | |||
93 | protected override void Dispose(bool disposing) | ||
94 | { | ||
95 | if(disposing) | ||
96 | { | ||
97 | if(components != null) | ||
98 | { | ||
99 | components.Dispose(); | ||
100 | } | ||
101 | } | ||
102 | base.Dispose(disposing); | ||
103 | } | ||
104 | |||
105 | #region Windows Form Designer generated code | ||
106 | /// <summary> | ||
107 | /// Required method for Designer support - do not modify | ||
108 | /// the contents of this method with the code editor. | ||
109 | /// </summary> | ||
110 | private void InitializeComponent() | ||
111 | { | ||
112 | this.components = new System.ComponentModel.Container(); | ||
113 | this.dataGrid = new System.Windows.Forms.DataGrid(); | ||
114 | this.treeView = new System.Windows.Forms.TreeView(); | ||
115 | this.toolPanel = new System.Windows.Forms.Panel(); | ||
116 | this.findStopButton = new System.Windows.Forms.Button(); | ||
117 | this.findButton = new System.Windows.Forms.Button(); | ||
118 | this.searchTreeCheckBox = new System.Windows.Forms.CheckBox(); | ||
119 | this.findTextBox = new System.Windows.Forms.TextBox(); | ||
120 | this.refreshButton = new System.Windows.Forms.Button(); | ||
121 | this.forwardButton = new System.Windows.Forms.Button(); | ||
122 | this.backButton = new System.Windows.Forms.Button(); | ||
123 | this.dataPanel = new System.Windows.Forms.Panel(); | ||
124 | this.splitter = new System.Windows.Forms.Splitter(); | ||
125 | this.gridLinkTip = new System.Windows.Forms.ToolTip(this.components); | ||
126 | ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).BeginInit(); | ||
127 | this.toolPanel.SuspendLayout(); | ||
128 | this.dataPanel.SuspendLayout(); | ||
129 | this.SuspendLayout(); | ||
130 | // | ||
131 | // dataGrid | ||
132 | // | ||
133 | this.dataGrid.DataMember = ""; | ||
134 | this.dataGrid.Dock = System.Windows.Forms.DockStyle.Fill; | ||
135 | this.dataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText; | ||
136 | this.dataGrid.Location = new System.Drawing.Point(230, 0); | ||
137 | this.dataGrid.Name = "dataGrid"; | ||
138 | this.dataGrid.ReadOnly = true; | ||
139 | this.dataGrid.SelectionBackColor = System.Drawing.SystemColors.Highlight; | ||
140 | this.dataGrid.Size = new System.Drawing.Size(562, 432); | ||
141 | this.dataGrid.TabIndex = 1; | ||
142 | this.dataGrid.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyDown); | ||
143 | this.dataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseDown); | ||
144 | this.dataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(this.dataGrid_KeyUp); | ||
145 | this.dataGrid.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGrid_MouseMove); | ||
146 | this.dataGrid.MouseLeave += new System.EventHandler(this.dataGrid_MouseLeave); | ||
147 | // | ||
148 | // treeView | ||
149 | // | ||
150 | this.treeView.Dock = System.Windows.Forms.DockStyle.Left; | ||
151 | this.treeView.HideSelection = false; | ||
152 | this.treeView.ImageIndex = -1; | ||
153 | this.treeView.Location = new System.Drawing.Point(0, 0); | ||
154 | this.treeView.Name = "treeView"; | ||
155 | this.treeView.SelectedImageIndex = -1; | ||
156 | this.treeView.Size = new System.Drawing.Size(224, 432); | ||
157 | this.treeView.TabIndex = 0; | ||
158 | this.treeView.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyDown); | ||
159 | this.treeView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown); | ||
160 | this.treeView.KeyUp += new System.Windows.Forms.KeyEventHandler(this.treeView_KeyUp); | ||
161 | this.treeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView_AfterSelect); | ||
162 | // | ||
163 | // toolPanel | ||
164 | // | ||
165 | this.toolPanel.Controls.Add(this.findStopButton); | ||
166 | this.toolPanel.Controls.Add(this.findButton); | ||
167 | this.toolPanel.Controls.Add(this.searchTreeCheckBox); | ||
168 | this.toolPanel.Controls.Add(this.findTextBox); | ||
169 | this.toolPanel.Controls.Add(this.refreshButton); | ||
170 | this.toolPanel.Controls.Add(this.forwardButton); | ||
171 | this.toolPanel.Controls.Add(this.backButton); | ||
172 | this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top; | ||
173 | this.toolPanel.Location = new System.Drawing.Point(0, 0); | ||
174 | this.toolPanel.Name = "toolPanel"; | ||
175 | this.toolPanel.Size = new System.Drawing.Size(792, 40); | ||
176 | this.toolPanel.TabIndex = 2; | ||
177 | // | ||
178 | // findStopButton | ||
179 | // | ||
180 | this.findStopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); | ||
181 | this.findStopButton.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
182 | this.findStopButton.Location = new System.Drawing.Point(704, 8); | ||
183 | this.findStopButton.Name = "findStopButton"; | ||
184 | this.findStopButton.Size = new System.Drawing.Size(72, 25); | ||
185 | this.findStopButton.TabIndex = 6; | ||
186 | this.findStopButton.Text = "Stop"; | ||
187 | this.findStopButton.Visible = false; | ||
188 | this.findStopButton.Click += new System.EventHandler(this.findStopButton_Click); | ||
189 | // | ||
190 | // findButton | ||
191 | // | ||
192 | this.findButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); | ||
193 | this.findButton.Enabled = false; | ||
194 | this.findButton.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
195 | this.findButton.Location = new System.Drawing.Point(624, 8); | ||
196 | this.findButton.Name = "findButton"; | ||
197 | this.findButton.Size = new System.Drawing.Size(72, 25); | ||
198 | this.findButton.TabIndex = 4; | ||
199 | this.findButton.Text = "Find"; | ||
200 | this.findButton.Click += new System.EventHandler(this.findButton_Click); | ||
201 | // | ||
202 | // searchTreeCheckBox | ||
203 | // | ||
204 | this.searchTreeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); | ||
205 | this.searchTreeCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
206 | this.searchTreeCheckBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); | ||
207 | this.searchTreeCheckBox.Location = new System.Drawing.Point(704, 10); | ||
208 | this.searchTreeCheckBox.Name = "searchTreeCheckBox"; | ||
209 | this.searchTreeCheckBox.Size = new System.Drawing.Size(80, 22); | ||
210 | this.searchTreeCheckBox.TabIndex = 5; | ||
211 | this.searchTreeCheckBox.Text = "In Subtree"; | ||
212 | this.searchTreeCheckBox.CheckedChanged += new System.EventHandler(this.searchTreeCheckBox_CheckedChanged); | ||
213 | // | ||
214 | // findTextBox | ||
215 | // | ||
216 | this.findTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); | ||
217 | this.findTextBox.Location = new System.Drawing.Point(344, 10); | ||
218 | this.findTextBox.Name = "findTextBox"; | ||
219 | this.findTextBox.Size = new System.Drawing.Size(272, 20); | ||
220 | this.findTextBox.TabIndex = 3; | ||
221 | this.findTextBox.Text = ""; | ||
222 | this.findTextBox.TextChanged += new System.EventHandler(this.findTextBox_TextChanged); | ||
223 | this.findTextBox.Enter += new System.EventHandler(this.findTextBox_Enter); | ||
224 | // | ||
225 | // refreshButton | ||
226 | // | ||
227 | this.refreshButton.Enabled = false; | ||
228 | this.refreshButton.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
229 | this.refreshButton.Location = new System.Drawing.Point(160, 8); | ||
230 | this.refreshButton.Name = "refreshButton"; | ||
231 | this.refreshButton.Size = new System.Drawing.Size(72, 25); | ||
232 | this.refreshButton.TabIndex = 2; | ||
233 | this.refreshButton.Text = "Refresh"; | ||
234 | this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click); | ||
235 | // | ||
236 | // forwardButton | ||
237 | // | ||
238 | this.forwardButton.Enabled = false; | ||
239 | this.forwardButton.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
240 | this.forwardButton.Location = new System.Drawing.Point(80, 8); | ||
241 | this.forwardButton.Name = "forwardButton"; | ||
242 | this.forwardButton.Size = new System.Drawing.Size(72, 25); | ||
243 | this.forwardButton.TabIndex = 1; | ||
244 | this.forwardButton.Text = "Forward"; | ||
245 | this.forwardButton.Click += new System.EventHandler(this.forwardButton_Click); | ||
246 | // | ||
247 | // backButton | ||
248 | // | ||
249 | this.backButton.Enabled = false; | ||
250 | this.backButton.FlatStyle = System.Windows.Forms.FlatStyle.System; | ||
251 | this.backButton.Location = new System.Drawing.Point(8, 8); | ||
252 | this.backButton.Name = "backButton"; | ||
253 | this.backButton.Size = new System.Drawing.Size(72, 25); | ||
254 | this.backButton.TabIndex = 0; | ||
255 | this.backButton.Text = "Back"; | ||
256 | this.backButton.Click += new System.EventHandler(this.backButton_Click); | ||
257 | // | ||
258 | // dataPanel | ||
259 | // | ||
260 | this.dataPanel.Controls.Add(this.dataGrid); | ||
261 | this.dataPanel.Controls.Add(this.splitter); | ||
262 | this.dataPanel.Controls.Add(this.treeView); | ||
263 | this.dataPanel.Dock = System.Windows.Forms.DockStyle.Fill; | ||
264 | this.dataPanel.Location = new System.Drawing.Point(0, 40); | ||
265 | this.dataPanel.Name = "dataPanel"; | ||
266 | this.dataPanel.Size = new System.Drawing.Size(792, 432); | ||
267 | this.dataPanel.TabIndex = 1; | ||
268 | // | ||
269 | // splitter | ||
270 | // | ||
271 | this.splitter.Location = new System.Drawing.Point(224, 0); | ||
272 | this.splitter.Name = "splitter"; | ||
273 | this.splitter.Size = new System.Drawing.Size(6, 432); | ||
274 | this.splitter.TabIndex = 2; | ||
275 | this.splitter.TabStop = false; | ||
276 | // | ||
277 | // Inventory | ||
278 | // | ||
279 | this.AcceptButton = this.findButton; | ||
280 | this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); | ||
281 | this.ClientSize = new System.Drawing.Size(792, 472); | ||
282 | this.Controls.Add(this.dataPanel); | ||
283 | this.Controls.Add(this.toolPanel); | ||
284 | this.MinimumSize = new System.Drawing.Size(700, 0); | ||
285 | this.Name = "Inventory"; | ||
286 | this.Text = "MSI Inventory"; | ||
287 | this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyDown); | ||
288 | this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Inventory_MouseDown); | ||
289 | this.Load += new System.EventHandler(this.Inventory_Load); | ||
290 | this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Inventory_KeyUp); | ||
291 | ((System.ComponentModel.ISupportInitialize)(this.dataGrid)).EndInit(); | ||
292 | this.toolPanel.ResumeLayout(false); | ||
293 | this.dataPanel.ResumeLayout(false); | ||
294 | this.ResumeLayout(false); | ||
295 | |||
296 | } | ||
297 | #endregion | ||
298 | |||
299 | |||
300 | #region DataProviders | ||
301 | |||
302 | private IInventoryDataProvider[] DataProviders | ||
303 | { | ||
304 | get | ||
305 | { | ||
306 | if(this.dataProviders == null) | ||
307 | { | ||
308 | ArrayList providerList = new ArrayList(); | ||
309 | providerList.AddRange(FindDataProviders(Assembly.GetExecutingAssembly())); | ||
310 | |||
311 | Uri codebase = new Uri(Assembly.GetExecutingAssembly().CodeBase); | ||
312 | if(codebase.IsFile) | ||
313 | { | ||
314 | foreach(string module in Directory.GetFiles(Path.GetDirectoryName(codebase.LocalPath), "*Inventory.dll")) | ||
315 | { | ||
316 | try | ||
317 | { | ||
318 | providerList.AddRange(FindDataProviders(Assembly.LoadFrom(module))); | ||
319 | } | ||
320 | catch(Exception) { } | ||
321 | } | ||
322 | } | ||
323 | |||
324 | this.dataProviders = (IInventoryDataProvider[]) providerList.ToArray(typeof(IInventoryDataProvider)); | ||
325 | } | ||
326 | return this.dataProviders; | ||
327 | } | ||
328 | } | ||
329 | |||
330 | private static IList FindDataProviders(Assembly assembly) | ||
331 | { | ||
332 | ArrayList providerList = new ArrayList(); | ||
333 | foreach(Type type in assembly.GetTypes()) | ||
334 | { | ||
335 | if(type.IsClass) | ||
336 | { | ||
337 | foreach(Type implementedInterface in type.GetInterfaces()) | ||
338 | { | ||
339 | if(implementedInterface.Equals(typeof(IInventoryDataProvider))) | ||
340 | { | ||
341 | try | ||
342 | { | ||
343 | providerList.Add(assembly.CreateInstance(type.FullName)); | ||
344 | } | ||
345 | catch(Exception) | ||
346 | { | ||
347 | // Data provider's constructor threw an exception for some reason. | ||
348 | // Well, now we can't get any data from that one. | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | } | ||
354 | return providerList; | ||
355 | } | ||
356 | |||
357 | #endregion | ||
358 | |||
359 | private void GoTo(string nodePath, DataGridCell cell) | ||
360 | { | ||
361 | lock(syncRoot) | ||
362 | { | ||
363 | if(this.tablesLoading == null) return; // The tree is being loaded | ||
364 | if(this.navigating) return; // This method is already on the callstack | ||
365 | |||
366 | DataView table = (DataView) this.data[nodePath]; | ||
367 | if(table != null && table == this.dataGrid.DataSource) | ||
368 | { | ||
369 | // Grid is already in view | ||
370 | if(!cell.Equals(anyCell)) this.dataGrid.CurrentCell = cell; | ||
371 | return; | ||
372 | } | ||
373 | if(cell.Equals(anyCell)) cell = zeroCell; | ||
374 | |||
375 | if(this.historyBack.Count == 0 || nodePath != (string) this.historyBack.Peek()) | ||
376 | { | ||
377 | this.historyBack.Push(nodePath); | ||
378 | if(this.cellHistoryBack.Count > 0 && this.historyForward != null) | ||
379 | { | ||
380 | this.cellHistoryBack.Pop(); | ||
381 | this.cellHistoryBack.Push(this.dataGrid.CurrentCell); | ||
382 | } | ||
383 | this.cellHistoryBack.Push(cell); | ||
384 | } | ||
385 | if(this.historyForward != null) | ||
386 | { | ||
387 | this.historyForward.Clear(); | ||
388 | this.cellHistoryForward.Clear(); | ||
389 | } | ||
390 | |||
391 | if(table != null || nodePath.Length == 0 || this.dataProviderMap[nodePath] == null) | ||
392 | { | ||
393 | this.dataGrid.CaptionText = nodePath; | ||
394 | this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption; | ||
395 | this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText; | ||
396 | this.dataGrid.DataSource = table; | ||
397 | this.dataGrid.CurrentCell = cell; | ||
398 | this.dataGrid.Focus(); | ||
399 | } | ||
400 | else | ||
401 | { | ||
402 | this.dataGrid.CaptionText = nodePath + " (loading...)"; | ||
403 | this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption; | ||
404 | this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText; | ||
405 | this.dataGrid.DataSource = table; | ||
406 | if(!this.tablesLoading.Contains(nodePath)) | ||
407 | { | ||
408 | this.tablesLoading.Add(nodePath); | ||
409 | this.SetCursor(); | ||
410 | #if SINGLETHREAD | ||
411 | this.LoadTable(nodePath); | ||
412 | #else | ||
413 | new WaitCallback(this.LoadTable).BeginInvoke(nodePath, null, null); | ||
414 | #endif | ||
415 | } | ||
416 | } | ||
417 | |||
418 | this.findButton.Enabled = this.findTextBox.Text.Length > 0 && !searching; | ||
419 | |||
420 | TreeNode treeNode = this.FindNode(nodePath); | ||
421 | if(treeNode != this.treeView.SelectedNode) | ||
422 | { | ||
423 | this.navigating = true; | ||
424 | this.treeView.SelectedNode = treeNode; | ||
425 | this.navigating = false; | ||
426 | } | ||
427 | } | ||
428 | } | ||
429 | |||
430 | private void LoadTable(object nodePathObj) | ||
431 | { | ||
432 | string nodePath = (string) nodePathObj; | ||
433 | IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath]; | ||
434 | DataView table = null; | ||
435 | if(dataProvider != null) | ||
436 | { | ||
437 | try | ||
438 | { | ||
439 | table = dataProvider.GetData(nodePath); | ||
440 | } | ||
441 | catch(Exception) | ||
442 | { | ||
443 | // Data provider threw an exception for some reason. | ||
444 | // Treat it like it returned no data. | ||
445 | } | ||
446 | } | ||
447 | |||
448 | lock(syncRoot) | ||
449 | { | ||
450 | if(this.tablesLoading == null || !tablesLoading.Contains(nodePath)) return; | ||
451 | if(table == null) | ||
452 | { | ||
453 | this.dataProviderMap.Remove(nodePath); | ||
454 | } | ||
455 | else | ||
456 | { | ||
457 | this.data[nodePath] = table; | ||
458 | } | ||
459 | this.tablesLoading.Remove(nodePath); | ||
460 | } | ||
461 | #if SINGLETHREAD | ||
462 | this.TableLoaded(nodePath); | ||
463 | #else | ||
464 | this.Invoke(new WaitCallback(this.TableLoaded), new object[] { nodePath }); | ||
465 | #endif | ||
466 | } | ||
467 | |||
468 | private void TableLoaded(object nodePathObj) | ||
469 | { | ||
470 | string nodePath = (string) nodePathObj; | ||
471 | lock(syncRoot) | ||
472 | { | ||
473 | this.LoadTableStyle(nodePath); | ||
474 | if(nodePath == this.CurrentNodePath) | ||
475 | { | ||
476 | this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption; | ||
477 | this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText; | ||
478 | this.dataGrid.CaptionText = nodePath; | ||
479 | this.dataGrid.DataSource = this.CurrentTable; | ||
480 | this.dataGrid.CurrentCell = (DataGridCell) this.cellHistoryBack.Peek(); | ||
481 | this.dataGrid.Focus(); | ||
482 | } | ||
483 | this.SetCursor(); | ||
484 | } | ||
485 | } | ||
486 | |||
487 | private void RefreshData() | ||
488 | { | ||
489 | lock(syncRoot) | ||
490 | { | ||
491 | this.GoTo("", zeroCell); | ||
492 | this.treeView.Nodes.Clear(); | ||
493 | this.dataGrid.TableStyles.Clear(); | ||
494 | this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption; | ||
495 | this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText; | ||
496 | this.SetControlsEnabled(false); | ||
497 | this.treeView.BeginUpdate(); | ||
498 | #if SINGLETHREAD | ||
499 | this.LoadTree(); | ||
500 | #else | ||
501 | new ThreadStart(this.LoadTree).BeginInvoke(null, null); | ||
502 | #endif | ||
503 | } | ||
504 | } | ||
505 | |||
506 | private void SetControlsEnabled(bool enabled) | ||
507 | { | ||
508 | this.backButton.Enabled = enabled && this.historyBack.Count > 1; | ||
509 | this.forwardButton.Enabled = enabled && this.historyForward.Count > 0; | ||
510 | this.refreshButton.Enabled = enabled; | ||
511 | this.findButton.Enabled = enabled && this.findTextBox.Text.Length > 0 && !searching; | ||
512 | } | ||
513 | |||
514 | private WaitCallback treeStatusCallback; | ||
515 | private int treeNodesLoaded; | ||
516 | private int treeNodesLoadedBase; | ||
517 | private string treeNodesLoading; | ||
518 | private void TreeLoadDataProviderStatus(int status, string currentNode) | ||
519 | { | ||
520 | if (currentNode != null) | ||
521 | { | ||
522 | this.treeNodesLoading = currentNode; | ||
523 | } | ||
524 | |||
525 | this.treeNodesLoaded = treeNodesLoadedBase + status; | ||
526 | string statusString = String.Format("Loading tree... " + this.treeNodesLoaded); | ||
527 | if (!String.IsNullOrEmpty(this.treeNodesLoading)) | ||
528 | { | ||
529 | statusString += ": " + treeNodesLoading; | ||
530 | } | ||
531 | |||
532 | #if SINGLETHREAD | ||
533 | treeStatusCallback(statusString); | ||
534 | #else | ||
535 | this.Invoke(treeStatusCallback, new object[] { statusString }); | ||
536 | #endif | ||
537 | } | ||
538 | |||
539 | private void UpdateTreeLoadStatus(object status) | ||
540 | { | ||
541 | if(status == null) | ||
542 | { | ||
543 | // Loading is complete. | ||
544 | this.treeView.EndUpdate(); | ||
545 | this.SetCursor(); | ||
546 | this.GoTo("Products", new DataGridCell(0, 0)); | ||
547 | this.SetControlsEnabled(true); | ||
548 | } | ||
549 | else | ||
550 | { | ||
551 | this.dataGrid.CaptionText = (string) status; | ||
552 | } | ||
553 | } | ||
554 | |||
555 | private void LoadTree() | ||
556 | { | ||
557 | lock(syncRoot) | ||
558 | { | ||
559 | if(this.tablesLoading == null) return; | ||
560 | this.tablesLoading = null; | ||
561 | this.dataProviderMap.Clear(); | ||
562 | this.data.Clear(); | ||
563 | this.Invoke(new ThreadStart(this.SetCursor)); | ||
564 | } | ||
565 | |||
566 | this.treeStatusCallback = new WaitCallback(UpdateTreeLoadStatus); | ||
567 | this.LoadTreeNodes(); | ||
568 | this.RenderTreeNodes(); | ||
569 | |||
570 | lock(syncRoot) | ||
571 | { | ||
572 | this.tablesLoading = new ArrayList(); | ||
573 | } | ||
574 | // Use a status of null to signal loading complete. | ||
575 | #if SINGLETHREAD | ||
576 | this.UpdateTreeLoadStatus(null); | ||
577 | #else | ||
578 | this.Invoke(new WaitCallback(this.UpdateTreeLoadStatus), new object[] { null }); | ||
579 | #endif | ||
580 | } | ||
581 | |||
582 | private void LoadTreeNodes() | ||
583 | { | ||
584 | #if SINGLETHREAD | ||
585 | this.treeStatusCallback("Loading tree... "); | ||
586 | #else | ||
587 | this.Invoke(this.treeStatusCallback, new object[] { "Loading tree... " }); | ||
588 | #endif | ||
589 | this.treeNodesLoaded = 0; | ||
590 | this.treeNodesLoading = null; | ||
591 | foreach(IInventoryDataProvider dataProvider in this.DataProviders) | ||
592 | { | ||
593 | this.treeNodesLoadedBase = this.treeNodesLoaded; | ||
594 | string[] nodePaths = null; | ||
595 | try | ||
596 | { | ||
597 | nodePaths = dataProvider.GetNodes(new InventoryDataLoadStatusCallback(this.TreeLoadDataProviderStatus)); | ||
598 | } | ||
599 | catch(Exception) | ||
600 | { | ||
601 | // Data provider threw an exception for some reason. | ||
602 | // Treat it like it returned no data. | ||
603 | } | ||
604 | if(nodePaths != null) | ||
605 | { | ||
606 | foreach(string nodePath in nodePaths) | ||
607 | { | ||
608 | if(!this.dataProviderMap.Contains(nodePath)) | ||
609 | { | ||
610 | this.dataProviderMap.Add(nodePath, dataProvider); | ||
611 | } | ||
612 | } | ||
613 | } | ||
614 | } | ||
615 | } | ||
616 | |||
617 | private void RenderTreeNodes() | ||
618 | { | ||
619 | #if SINGLETHREAD | ||
620 | this.treeStatusCallback("Rendering tree... "); | ||
621 | #else | ||
622 | this.Invoke(this.treeStatusCallback, new object[] { "Rendering tree... " }); | ||
623 | #endif | ||
624 | this.treeNodesLoaded = 0; | ||
625 | foreach(DictionaryEntry nodePathAndProvider in this.dataProviderMap) | ||
626 | { | ||
627 | string nodePath = (string) nodePathAndProvider.Key; | ||
628 | #if SINGLETHREAD | ||
629 | this.AddNode(nodePath); | ||
630 | #else | ||
631 | this.Invoke(new WaitCallback(this.AddNode), new object[] { nodePath }); | ||
632 | #endif | ||
633 | } | ||
634 | } | ||
635 | |||
636 | private void LoadTableStyle(string nodePath) | ||
637 | { | ||
638 | DataView table = (DataView) this.data[nodePath]; | ||
639 | if(table != null) | ||
640 | { | ||
641 | DataGridTableStyle tableStyle = this.dataGrid.TableStyles[table.Table.TableName]; | ||
642 | if(tableStyle == null) | ||
643 | { | ||
644 | tableStyle = new DataGridTableStyle(); | ||
645 | tableStyle.MappingName = table.Table.TableName; | ||
646 | tableStyle.RowHeadersVisible = true; | ||
647 | this.dataGrid.TableStyles.Add(tableStyle); | ||
648 | } | ||
649 | foreach(DataColumn column in table.Table.Columns) | ||
650 | { | ||
651 | if(!tableStyle.GridColumnStyles.Contains(column.ColumnName)) | ||
652 | { | ||
653 | string colStyle = (string) ColumnResources.GetObject(column.ColumnName, CultureInfo.InvariantCulture); | ||
654 | if(colStyle != null) | ||
655 | { | ||
656 | string[] colStyleParts = colStyle.Split(','); | ||
657 | DataGridColumnStyle columnStyle = (colStyleParts.Length > 2 && colStyleParts[2] == "bool" | ||
658 | ? (DataGridColumnStyle) new DataGridBoolColumn() : (DataGridColumnStyle) new DataGridTextBoxColumn()); | ||
659 | try { if(colStyleParts.Length > 1) columnStyle.Width = Int32.Parse(colStyleParts[1]); } | ||
660 | catch(FormatException) { } | ||
661 | columnStyle.HeaderText = colStyleParts[0]; | ||
662 | columnStyle.MappingName = column.ColumnName; | ||
663 | tableStyle.GridColumnStyles.Add(columnStyle); | ||
664 | } | ||
665 | } | ||
666 | } | ||
667 | } | ||
668 | } | ||
669 | |||
670 | private static ResourceManager ColumnResources | ||
671 | { | ||
672 | get | ||
673 | { | ||
674 | if(columnResources == null) | ||
675 | { | ||
676 | columnResources = new ResourceManager(typeof(Inventory).Name + ".Columns", typeof(Inventory).Assembly); | ||
677 | } | ||
678 | return columnResources; | ||
679 | } | ||
680 | } | ||
681 | private static ResourceManager columnResources; | ||
682 | |||
683 | private void AddNode(object nodePathObj) | ||
684 | { | ||
685 | string nodePath = (string) nodePathObj; | ||
686 | string[] path = nodePath.Split('\\'); | ||
687 | TreeNodeCollection nodes = this.treeView.Nodes; | ||
688 | TreeNode node = null; | ||
689 | foreach(string pathPart in path) | ||
690 | { | ||
691 | node = null; | ||
692 | for(int i = 0; i < nodes.Count; i++) | ||
693 | { | ||
694 | int c = string.CompareOrdinal(nodes[i].Text, pathPart); | ||
695 | if(c == 0) | ||
696 | { | ||
697 | node = nodes[i]; | ||
698 | break; | ||
699 | } | ||
700 | else if(c > 0) | ||
701 | { | ||
702 | node = new TreeNode(pathPart); | ||
703 | nodes.Insert(i, node); | ||
704 | break; | ||
705 | } | ||
706 | } | ||
707 | if(node == null) | ||
708 | { | ||
709 | node = new TreeNode(pathPart); | ||
710 | nodes.Add(node); | ||
711 | } | ||
712 | nodes = node.Nodes; | ||
713 | } | ||
714 | if(++this.treeNodesLoaded % 1000 == 0) | ||
715 | { | ||
716 | this.UpdateTreeLoadStatus("Rendering tree... " + | ||
717 | (100 * this.treeNodesLoaded / this.dataProviderMap.Count) + "%"); | ||
718 | } | ||
719 | } | ||
720 | |||
721 | public string CurrentNodePath | ||
722 | { | ||
723 | get | ||
724 | { | ||
725 | TreeNode currentNode = this.treeView.SelectedNode; | ||
726 | return currentNode != null ? currentNode.FullPath : null; | ||
727 | } | ||
728 | } | ||
729 | |||
730 | public DataView CurrentTable | ||
731 | { | ||
732 | get | ||
733 | { | ||
734 | string currentNodePath = this.CurrentNodePath; | ||
735 | return currentNodePath != null ? (DataView) this.data[this.CurrentNodePath] : null; | ||
736 | } | ||
737 | } | ||
738 | |||
739 | private TreeNode FindNode(string nodePath) | ||
740 | { | ||
741 | if(nodePath == null) return null; | ||
742 | string[] path = nodePath.Split('\\'); | ||
743 | TreeNodeCollection nodes = this.treeView.Nodes; | ||
744 | TreeNode node = null; | ||
745 | foreach(string pathPart in path) | ||
746 | { | ||
747 | node = null; | ||
748 | for(int i = 0; i < nodes.Count; i++) | ||
749 | { | ||
750 | if(nodes[i].Text == pathPart) | ||
751 | { | ||
752 | node = nodes[i]; | ||
753 | break; | ||
754 | } | ||
755 | } | ||
756 | if(node != null) | ||
757 | { | ||
758 | nodes = node.Nodes; | ||
759 | } | ||
760 | } | ||
761 | return node; | ||
762 | } | ||
763 | |||
764 | private void dataGrid_MouseDown(object sender, MouseEventArgs e) | ||
765 | { | ||
766 | Keys modKeys = Control.ModifierKeys; | ||
767 | if(e.Button == MouseButtons.Left && (modKeys & (Keys.Shift | Keys.Control)) == 0) | ||
768 | { | ||
769 | DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y); | ||
770 | string link = this.GetLinkForGridHit(hit); | ||
771 | if(link != null) | ||
772 | { | ||
773 | TreeNode node = this.FindNode(link); | ||
774 | if(node != null) | ||
775 | { | ||
776 | this.treeView.SelectedNode = node; | ||
777 | node.Expand(); | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | this.Inventory_MouseDown(sender, e); | ||
782 | } | ||
783 | |||
784 | private void dataGrid_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) | ||
785 | { | ||
786 | //this.gridLinkTip.SetToolTip(this.dataGrid, null); | ||
787 | DataGrid.HitTestInfo hit = this.dataGrid.HitTest(e.X, e.Y); | ||
788 | if(hit.Type == DataGrid.HitTestType.RowHeader) | ||
789 | { | ||
790 | string link = this.GetLinkForGridHit(hit); | ||
791 | if(link != null) | ||
792 | { | ||
793 | this.mouseOverGridLink = true; | ||
794 | this.SetCursor(); | ||
795 | return; | ||
796 | } | ||
797 | } | ||
798 | else if(this.mouseOverGridLink) | ||
799 | { | ||
800 | this.mouseOverGridLink = false; | ||
801 | this.SetCursor(); | ||
802 | } | ||
803 | } | ||
804 | |||
805 | private void dataGrid_MouseLeave(object sender, System.EventArgs e) | ||
806 | { | ||
807 | this.mouseOverGridLink = false; | ||
808 | this.SetCursor(); | ||
809 | } | ||
810 | |||
811 | private string GetLinkForGridHit(DataGrid.HitTestInfo hit) | ||
812 | { | ||
813 | if(hit.Type == DataGrid.HitTestType.RowHeader && this.tablesLoading != null) | ||
814 | { | ||
815 | string nodePath = this.CurrentNodePath; | ||
816 | DataView table = (DataView) this.data[nodePath]; | ||
817 | if(table != null) | ||
818 | { | ||
819 | DataRow row = table[hit.Row].Row; | ||
820 | IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath]; | ||
821 | return dataProvider.GetLink(nodePath, table[hit.Row].Row); | ||
822 | } | ||
823 | } | ||
824 | return null; | ||
825 | } | ||
826 | |||
827 | private void HistoryBack() | ||
828 | { | ||
829 | lock(syncRoot) | ||
830 | { | ||
831 | if(this.historyBack.Count > 1) | ||
832 | { | ||
833 | string nodePath = (string) this.historyBack.Pop(); | ||
834 | this.cellHistoryBack.Pop(); | ||
835 | DataGridCell cell = this.dataGrid.CurrentCell; | ||
836 | Stack saveForward = this.historyForward; | ||
837 | this.historyForward = null; | ||
838 | this.GoTo((string) this.historyBack.Pop(), (DataGridCell) this.cellHistoryBack.Pop()); | ||
839 | this.historyForward = saveForward; | ||
840 | this.historyForward.Push(nodePath); | ||
841 | this.cellHistoryForward.Push(cell); | ||
842 | this.backButton.Enabled = this.historyBack.Count > 1; | ||
843 | this.forwardButton.Enabled = this.historyForward.Count > 0; | ||
844 | } | ||
845 | } | ||
846 | } | ||
847 | |||
848 | private void HistoryForward() | ||
849 | { | ||
850 | lock(syncRoot) | ||
851 | { | ||
852 | if(this.historyForward.Count > 0) | ||
853 | { | ||
854 | string nodePath = (string) this.historyForward.Pop(); | ||
855 | DataGridCell cell = (DataGridCell) this.cellHistoryForward.Pop(); | ||
856 | Stack saveForward = this.historyForward; | ||
857 | this.historyForward = null; | ||
858 | this.GoTo(nodePath, cell); | ||
859 | this.historyForward = saveForward; | ||
860 | this.backButton.Enabled = this.historyBack.Count > 1; | ||
861 | this.forwardButton.Enabled = this.historyForward.Count > 0; | ||
862 | } | ||
863 | } | ||
864 | } | ||
865 | |||
866 | #region Find | ||
867 | |||
868 | private void Find() | ||
869 | { | ||
870 | this.BeginFind(); | ||
871 | object[] findNextArgs = new object[] { this.CurrentNodePath, this.dataGrid.CurrentCell, this.treeView.SelectedNode }; | ||
872 | #if SINGLETHREAD | ||
873 | this.FindNext(findNextArgs); | ||
874 | #else | ||
875 | new WaitCallback(this.FindNext).BeginInvoke(findNextArgs, null, null); | ||
876 | #endif | ||
877 | } | ||
878 | |||
879 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] | ||
880 | private void FindNext(object start) | ||
881 | { | ||
882 | string nodePath = (string) ((object[]) start)[0]; | ||
883 | DataGridCell startCell = (DataGridCell) ((object[]) start)[1]; | ||
884 | TreeNode searchNode = (TreeNode) ((object[]) start)[2]; | ||
885 | DataGridCell endCell = startCell; | ||
886 | |||
887 | string searchString = this.findTextBox.Text; | ||
888 | if(searchString.Length == 0) return; | ||
889 | |||
890 | bool ignoreCase = true; // TODO: make this a configurable option? | ||
891 | if(ignoreCase) searchString = searchString.ToLowerInvariant(); | ||
892 | |||
893 | if(!this.searchTreeCheckBox.Checked) | ||
894 | { | ||
895 | DataGridCell foundCell; | ||
896 | startCell.ColumnNumber++; | ||
897 | if(FindInTable((DataView) this.data[nodePath], searchString, ignoreCase, | ||
898 | startCell, startCell, true, out foundCell)) | ||
899 | { | ||
900 | #if SINGLETHREAD | ||
901 | this.EndFind(new object[] { nodePath, foundCell }); | ||
902 | #else | ||
903 | this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodePath, foundCell } }); | ||
904 | #endif | ||
905 | return; | ||
906 | } | ||
907 | } | ||
908 | else | ||
909 | { | ||
910 | if(this.continueSearchRoot != null) | ||
911 | { | ||
912 | searchNode = this.FindNode(this.continueSearchRoot); | ||
913 | startCell = this.continueSearchCell; | ||
914 | endCell = this.continueSearchEndCell; | ||
915 | } | ||
916 | else | ||
917 | { | ||
918 | this.continueSearchRoot = searchNode.FullPath; | ||
919 | this.continueSearchPath = this.continueSearchRoot; | ||
920 | this.continueSearchEndCell = endCell; | ||
921 | } | ||
922 | //if(searchNode == null) return; | ||
923 | ArrayList nodesList = new ArrayList(); | ||
924 | nodesList.Add(searchNode); | ||
925 | this.GetFlatTreeNodes(searchNode.Nodes, nodesList, true, this.continueSearchRoot); | ||
926 | TreeNode[] nodes = (TreeNode[]) nodesList.ToArray(typeof(TreeNode)); | ||
927 | int startNode = nodesList.IndexOf(this.FindNode(this.continueSearchPath)); | ||
928 | DataGridCell foundCell; | ||
929 | startCell.ColumnNumber++; | ||
930 | for(int i = startNode; i < nodes.Length; i++) | ||
931 | { | ||
932 | if(this.stopSearch) break; | ||
933 | DataGridCell startCellOnThisNode = zeroCell; | ||
934 | if(i == startNode) startCellOnThisNode = startCell; | ||
935 | DataView table = this.GetTableForSearch(nodes[i].FullPath); | ||
936 | if(table != null) | ||
937 | { | ||
938 | if(FindInTable(table, searchString, ignoreCase, startCellOnThisNode, zeroCell, false, out foundCell)) | ||
939 | { | ||
940 | #if SINGLETHREAD | ||
941 | this.EndFind(new object[] { nodes[i].FullPath, foundCell }); | ||
942 | #else | ||
943 | this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { nodes[i].FullPath, foundCell } }); | ||
944 | #endif | ||
945 | return; | ||
946 | } | ||
947 | } | ||
948 | } | ||
949 | if(!this.stopSearch) | ||
950 | { | ||
951 | DataView table = this.GetTableForSearch(searchNode.FullPath); | ||
952 | if(table != null) | ||
953 | { | ||
954 | if(FindInTable(table, searchString, ignoreCase, zeroCell, endCell, false, out foundCell)) | ||
955 | { | ||
956 | #if SINGLETHREAD | ||
957 | this.EndFind(new object[] { searchNode.FullPath, foundCell }); | ||
958 | #else | ||
959 | this.Invoke(new WaitCallback(this.EndFind), new object[] { new object[] { searchNode.FullPath, foundCell } }); | ||
960 | #endif | ||
961 | return; | ||
962 | } | ||
963 | } | ||
964 | } | ||
965 | } | ||
966 | #if SINGLETHREAD | ||
967 | this.EndFind(null); | ||
968 | #else | ||
969 | this.Invoke(new WaitCallback(this.EndFind), new object[] { null }); | ||
970 | #endif | ||
971 | } | ||
972 | |||
973 | private DataView GetTableForSearch(string nodePath) | ||
974 | { | ||
975 | DataView table = (DataView) this.data[nodePath]; | ||
976 | string status = nodePath; | ||
977 | if(table == null) status = status + " (loading)"; | ||
978 | #if SINGLETHREAD | ||
979 | this.FindStatus(nodePath); | ||
980 | #else | ||
981 | this.Invoke(new WaitCallback(this.FindStatus), new object[] { status }); | ||
982 | #endif | ||
983 | if(table == null) | ||
984 | { | ||
985 | this.tablesLoading.Add(nodePath); | ||
986 | this.Invoke(new ThreadStart(this.SetCursor)); | ||
987 | this.LoadTable(nodePath); | ||
988 | table = (DataView) this.data[nodePath]; | ||
989 | } | ||
990 | return table; | ||
991 | } | ||
992 | |||
993 | private void GetFlatTreeNodes(TreeNodeCollection nodes, IList resultsList, bool searchable, string searchRoot) | ||
994 | { | ||
995 | foreach(TreeNode node in nodes) | ||
996 | { | ||
997 | string nodePath = node.FullPath; | ||
998 | IInventoryDataProvider dataProvider = (IInventoryDataProvider) this.dataProviderMap[nodePath]; | ||
999 | if(!searchable || (dataProvider != null && dataProvider.IsNodeSearchable(searchRoot, nodePath))) | ||
1000 | { | ||
1001 | resultsList.Add(node); | ||
1002 | } | ||
1003 | GetFlatTreeNodes(node.Nodes, resultsList, searchable, searchRoot); | ||
1004 | } | ||
1005 | } | ||
1006 | |||
1007 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] | ||
1008 | private bool FindInTable(DataView table, string searchString, bool lowerCase, | ||
1009 | DataGridCell startCell, DataGridCell endCell, bool wrap, out DataGridCell foundCell) | ||
1010 | { | ||
1011 | foundCell = new DataGridCell(-1, -1); | ||
1012 | if(table == null) return false; | ||
1013 | if(startCell.RowNumber < 0) startCell.RowNumber = 0; | ||
1014 | if(startCell.ColumnNumber < 0) startCell.ColumnNumber = 0; | ||
1015 | for(int searchRow = startCell.RowNumber; searchRow < table.Count; searchRow++) | ||
1016 | { | ||
1017 | if(this.stopSearch) break; | ||
1018 | if(endCell.RowNumber > startCell.RowNumber && searchRow > endCell.RowNumber) break; | ||
1019 | |||
1020 | DataRowView tableRow = table[searchRow]; | ||
1021 | for(int searchCol = (searchRow == startCell.RowNumber | ||
1022 | ? startCell.ColumnNumber : 0); searchCol < table.Table.Columns.Count; searchCol++) | ||
1023 | { | ||
1024 | if(this.stopSearch) break; | ||
1025 | if(endCell.RowNumber > startCell.RowNumber && searchRow == endCell.RowNumber | ||
1026 | && searchCol >= endCell.ColumnNumber) break; | ||
1027 | |||
1028 | string value = tableRow[searchCol].ToString(); | ||
1029 | if(lowerCase) value = value.ToLowerInvariant(); | ||
1030 | if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0) | ||
1031 | { | ||
1032 | foundCell.RowNumber = searchRow; | ||
1033 | foundCell.ColumnNumber = searchCol; | ||
1034 | return true; | ||
1035 | } | ||
1036 | } | ||
1037 | } | ||
1038 | if(wrap) | ||
1039 | { | ||
1040 | for(int searchRow = 0; searchRow <= endCell.RowNumber; searchRow++) | ||
1041 | { | ||
1042 | if(this.stopSearch) break; | ||
1043 | DataRowView tableRow = table[searchRow]; | ||
1044 | for(int searchCol = 0; searchCol < (searchRow == endCell.RowNumber | ||
1045 | ? endCell.ColumnNumber : table.Table.Columns.Count); searchCol++) | ||
1046 | { | ||
1047 | if(this.stopSearch) break; | ||
1048 | string value = tableRow[searchCol].ToString(); | ||
1049 | if(lowerCase) value = value.ToLowerInvariant(); | ||
1050 | if(value.IndexOf(searchString, StringComparison.Ordinal) >= 0) | ||
1051 | { | ||
1052 | foundCell.RowNumber = searchRow; | ||
1053 | foundCell.ColumnNumber = searchCol; | ||
1054 | return true; | ||
1055 | } | ||
1056 | } | ||
1057 | } | ||
1058 | } | ||
1059 | return false; | ||
1060 | } | ||
1061 | |||
1062 | private void BeginFind() | ||
1063 | { | ||
1064 | lock(syncRoot) | ||
1065 | { | ||
1066 | this.findButton.Enabled = false; | ||
1067 | this.findButton.Text = "Searching..."; | ||
1068 | this.findTextBox.Enabled = false; | ||
1069 | this.searchTreeCheckBox.Visible = false; | ||
1070 | this.findStopButton.Visible = true; | ||
1071 | this.refreshButton.Enabled = false; | ||
1072 | this.searching = true; | ||
1073 | this.stopSearch = false; | ||
1074 | this.SetCursor(); | ||
1075 | } | ||
1076 | } | ||
1077 | |||
1078 | private void FindStatus(object status) | ||
1079 | { | ||
1080 | lock(syncRoot) | ||
1081 | { | ||
1082 | this.dataGrid.CaptionText = "Searching... " + (string) status; | ||
1083 | this.dataGrid.CaptionBackColor = SystemColors.InactiveCaption; | ||
1084 | this.dataGrid.CaptionForeColor = SystemColors.InactiveCaptionText; | ||
1085 | } | ||
1086 | } | ||
1087 | |||
1088 | private void EndFind(object result) | ||
1089 | { | ||
1090 | lock(syncRoot) | ||
1091 | { | ||
1092 | this.searching = false; | ||
1093 | this.refreshButton.Enabled = true; | ||
1094 | this.findStopButton.Visible = false; | ||
1095 | this.searchTreeCheckBox.Visible = true; | ||
1096 | this.findTextBox.Enabled = true; | ||
1097 | this.findButton.Text = "Find"; | ||
1098 | this.findButton.Enabled = true; | ||
1099 | this.dataGrid.CaptionBackColor = SystemColors.ActiveCaption; | ||
1100 | this.dataGrid.CaptionForeColor = SystemColors.ActiveCaptionText; | ||
1101 | this.dataGrid.CaptionText = this.CurrentNodePath; | ||
1102 | if(result != null) | ||
1103 | { | ||
1104 | string nodePath = (string) ((object[]) result)[0]; | ||
1105 | DataGridCell foundCell = (DataGridCell) ((object[]) result)[1]; | ||
1106 | this.GoTo(nodePath, foundCell); | ||
1107 | this.dataGrid.Focus(); | ||
1108 | this.continueSearchPath = nodePath; | ||
1109 | this.continueSearchCell = foundCell; | ||
1110 | if(this.searchTreeCheckBox.Checked) this.searchTreeCheckBox.Text = "Continue"; | ||
1111 | } | ||
1112 | else | ||
1113 | { | ||
1114 | this.continueSearchRoot = null; | ||
1115 | this.continueSearchPath = null; | ||
1116 | this.searchTreeCheckBox.Text = "In Subtree"; | ||
1117 | } | ||
1118 | this.SetCursor(); | ||
1119 | } | ||
1120 | } | ||
1121 | |||
1122 | private void SetCursor() | ||
1123 | { | ||
1124 | if(this.mouseOverGridLink) | ||
1125 | { | ||
1126 | Keys modKeys = Control.ModifierKeys; | ||
1127 | if((modKeys & (Keys.Shift | Keys.Control)) == 0) | ||
1128 | { | ||
1129 | this.Cursor = Cursors.Hand; | ||
1130 | return; | ||
1131 | } | ||
1132 | } | ||
1133 | if(this.tablesLoading == null || this.tablesLoading.Count > 0 || this.searching) | ||
1134 | { | ||
1135 | this.Cursor = Cursors.AppStarting; | ||
1136 | return; | ||
1137 | } | ||
1138 | this.Cursor = Cursors.Arrow; | ||
1139 | } | ||
1140 | |||
1141 | #endregion | ||
1142 | |||
1143 | #region EventHandlers | ||
1144 | |||
1145 | private void Inventory_Load(object sender, System.EventArgs e) | ||
1146 | { | ||
1147 | this.RefreshData(); | ||
1148 | } | ||
1149 | private void refreshButton_Click(object sender, System.EventArgs e) | ||
1150 | { | ||
1151 | this.RefreshData(); | ||
1152 | } | ||
1153 | private void Inventory_MouseDown(object sender, MouseEventArgs e) | ||
1154 | { | ||
1155 | if(e.Button == MouseButtons.XButton1) this.HistoryBack(); | ||
1156 | else if(e.Button == MouseButtons.XButton2) this.HistoryForward(); | ||
1157 | } | ||
1158 | private void Inventory_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1159 | { | ||
1160 | this.SetCursor(); | ||
1161 | if(e.KeyCode == Keys.F3) this.Find(); | ||
1162 | else if(e.KeyCode == Keys.F && (e.Modifiers | Keys.Control) != 0) this.findTextBox.Focus(); | ||
1163 | else if(e.KeyCode == Keys.BrowserBack) this.HistoryBack(); | ||
1164 | else if(e.KeyCode == Keys.BrowserForward) this.HistoryForward(); | ||
1165 | else return; | ||
1166 | e.Handled = true; | ||
1167 | } | ||
1168 | private void treeView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1169 | { | ||
1170 | this.Inventory_KeyDown(sender, e); | ||
1171 | } | ||
1172 | private void dataGrid_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1173 | { | ||
1174 | this.Inventory_KeyDown(sender, e); | ||
1175 | } | ||
1176 | private void Inventory_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1177 | { | ||
1178 | this.SetCursor(); | ||
1179 | } | ||
1180 | private void treeView_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1181 | { | ||
1182 | this.Inventory_KeyDown(sender, e); | ||
1183 | } | ||
1184 | private void dataGrid_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) | ||
1185 | { | ||
1186 | this.Inventory_KeyDown(sender, e); | ||
1187 | } | ||
1188 | private void treeView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e) | ||
1189 | { | ||
1190 | this.GoTo(e.Node.FullPath, anyCell); | ||
1191 | } | ||
1192 | private void backButton_Click(object sender, System.EventArgs e) | ||
1193 | { | ||
1194 | this.HistoryBack(); | ||
1195 | } | ||
1196 | private void forwardButton_Click(object sender, System.EventArgs e) | ||
1197 | { | ||
1198 | this.HistoryForward(); | ||
1199 | } | ||
1200 | private void findTextBox_TextChanged(object sender, System.EventArgs e) | ||
1201 | { | ||
1202 | this.findButton.Enabled = this.findTextBox.Text.Length > 0 && | ||
1203 | this.tablesLoading != null && this.treeView.SelectedNode != null && !searching; | ||
1204 | this.searchTreeCheckBox.Text = "In Subtree"; | ||
1205 | this.continueSearchRoot = null; | ||
1206 | } | ||
1207 | private void findButton_Click(object sender, System.EventArgs e) | ||
1208 | { | ||
1209 | this.Find(); | ||
1210 | } | ||
1211 | private void findTextBox_Enter(object sender, System.EventArgs e) | ||
1212 | { | ||
1213 | findTextBox.SelectAll(); | ||
1214 | } | ||
1215 | private void findStopButton_Click(object sender, System.EventArgs e) | ||
1216 | { | ||
1217 | this.stopSearch = true; | ||
1218 | } | ||
1219 | |||
1220 | private void searchTreeCheckBox_CheckedChanged(object sender, System.EventArgs e) | ||
1221 | { | ||
1222 | if(!searchTreeCheckBox.Checked && searchTreeCheckBox.Text == "Continue") | ||
1223 | { | ||
1224 | this.searchTreeCheckBox.Text = "In Subtree"; | ||
1225 | } | ||
1226 | } | ||
1227 | |||
1228 | #endregion | ||
1229 | |||
1230 | } | ||
1231 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Data; | ||
6 | using System.Text; | ||
7 | using System.Collections; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Globalization; | ||
10 | using System.Windows.Forms; | ||
11 | using Microsoft.Win32; | ||
12 | using WixToolset.Dtf.WindowsInstaller; | ||
13 | using View = WixToolset.Dtf.WindowsInstaller.View; | ||
14 | |||
15 | namespace WixToolset.Dtf.Samples.Inventory | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Provides inventory data about components of products installed on the system. | ||
19 | /// </summary> | ||
20 | public class ComponentsInventory : IInventoryDataProvider | ||
21 | { | ||
22 | private static object syncRoot = new object(); | ||
23 | |||
24 | public ComponentsInventory() | ||
25 | { | ||
26 | } | ||
27 | |||
28 | public string Description | ||
29 | { | ||
30 | get { return "Components of installed products"; } | ||
31 | } | ||
32 | |||
33 | private Hashtable componentProductsMap; | ||
34 | |||
35 | public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback) | ||
36 | { | ||
37 | ArrayList nodes = new ArrayList(); | ||
38 | componentProductsMap = new Hashtable(); | ||
39 | foreach(ProductInstallation product in ProductInstallation.AllProducts) | ||
40 | { | ||
41 | string productName = MsiUtils.GetProductName(product.ProductCode); | ||
42 | statusCallback(nodes.Count, String.Format(@"Products\{0}", productName)); | ||
43 | |||
44 | try | ||
45 | { | ||
46 | IntPtr hWnd = IntPtr.Zero; | ||
47 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
48 | lock(syncRoot) // Only one Installer session can be active at a time | ||
49 | { | ||
50 | using (Session session = Installer.OpenProduct(product.ProductCode)) | ||
51 | { | ||
52 | statusCallback(nodes.Count, String.Format(@"Products\{0}\Features", productName)); | ||
53 | IList<string> features = session.Database.ExecuteStringQuery("SELECT `Feature` FROM `Feature`"); | ||
54 | string[] featuresArray = new string[features.Count]; | ||
55 | features.CopyTo(featuresArray, 0); | ||
56 | Array.Sort(featuresArray, 0, featuresArray.Length, StringComparer.OrdinalIgnoreCase); | ||
57 | foreach (string feature in featuresArray) | ||
58 | { | ||
59 | nodes.Add(String.Format(@"Products\{0}\Features\{1}", productName, feature)); | ||
60 | } | ||
61 | statusCallback(nodes.Count, String.Format(@"Products\{0}\Components", productName)); | ||
62 | nodes.Add(String.Format(@"Products\{0}\Components", productName)); | ||
63 | IList<string> components = session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`"); | ||
64 | for (int i = 0; i < components.Count; i++) | ||
65 | { | ||
66 | string component = components[i]; | ||
67 | if (component.Length > 0) | ||
68 | { | ||
69 | nodes.Add(String.Format(@"Products\{0}\Components\{1}", productName, component)); | ||
70 | ArrayList sharingProducts = (ArrayList) componentProductsMap[component]; | ||
71 | if (sharingProducts == null) | ||
72 | { | ||
73 | sharingProducts = new ArrayList(); | ||
74 | componentProductsMap[component] = sharingProducts; | ||
75 | } | ||
76 | sharingProducts.Add(product.ProductCode); | ||
77 | } | ||
78 | if (i % 100 == 0) statusCallback(nodes.Count, null); | ||
79 | } | ||
80 | nodes.Add(String.Format(@"Products\{0}\Files", productName)); | ||
81 | nodes.Add(String.Format(@"Products\{0}\Registry", productName)); | ||
82 | statusCallback(nodes.Count, String.Empty); | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | catch(InstallerException) { } | ||
87 | } | ||
88 | statusCallback(nodes.Count, @"Products\...\Components\...\Sharing"); | ||
89 | foreach (DictionaryEntry componentProducts in componentProductsMap) | ||
90 | { | ||
91 | string component = (string) componentProducts.Key; | ||
92 | ArrayList products = (ArrayList) componentProducts.Value; | ||
93 | if(products.Count > 1) | ||
94 | { | ||
95 | foreach(string productCode in products) | ||
96 | { | ||
97 | nodes.Add(String.Format(@"Products\{0}\Components\{1}\Sharing", MsiUtils.GetProductName(productCode), component)); | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | statusCallback(nodes.Count, String.Empty); | ||
102 | return (string[]) nodes.ToArray(typeof(string)); | ||
103 | } | ||
104 | |||
105 | public bool IsNodeSearchable(string searchRoot, string searchNode) | ||
106 | { | ||
107 | string[] rootPath = searchRoot.Split('\\'); | ||
108 | string[] nodePath = searchNode.Split('\\'); | ||
109 | if(rootPath.Length < 3 && nodePath.Length >= 3 && nodePath[0] == "Products" && nodePath[2] == "Components") | ||
110 | { | ||
111 | // When searching an entire product, don't search the "Components" subtree -- | ||
112 | // it just has duplicate data from the Files and Registry table. And if you | ||
113 | // really want to know about the component, it's only a click away from | ||
114 | // those other tables. | ||
115 | return false; | ||
116 | } | ||
117 | return true; | ||
118 | } | ||
119 | |||
120 | public DataView GetData(string nodePath) | ||
121 | { | ||
122 | string[] path = nodePath.Split('\\'); | ||
123 | |||
124 | if(path.Length == 4 && path[0] == "Products" && path[2] == "Features") | ||
125 | { | ||
126 | return GetFeatureComponentsData(MsiUtils.GetProductCode(path[1]), path[3]); | ||
127 | } | ||
128 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Components") | ||
129 | { | ||
130 | return GetProductComponentsData(MsiUtils.GetProductCode(path[1])); | ||
131 | } | ||
132 | else if(path.Length == 4 && path[0] == "Products" && path[2] == "Components") | ||
133 | { | ||
134 | return GetComponentData(MsiUtils.GetProductCode(path[1]), path[3]); | ||
135 | } | ||
136 | else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing") | ||
137 | { | ||
138 | return GetComponentProductsData(path[3]); | ||
139 | } | ||
140 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files") | ||
141 | { | ||
142 | return GetProductFilesData(MsiUtils.GetProductCode(path[1])); | ||
143 | } | ||
144 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry") | ||
145 | { | ||
146 | return GetProductRegistryData(MsiUtils.GetProductCode(path[1])); | ||
147 | } | ||
148 | return null; | ||
149 | } | ||
150 | |||
151 | public DataView GetComponentData(string productCode, string componentCode) | ||
152 | { | ||
153 | DataTable table = new DataTable("ProductComponentItems"); | ||
154 | table.Locale = CultureInfo.InvariantCulture; | ||
155 | table.Columns.Add("ProductComponentItemsIsKey", typeof(bool)); | ||
156 | table.Columns.Add("ProductComponentItemsKey", typeof(string)); | ||
157 | table.Columns.Add("ProductComponentItemsPath", typeof(string)); | ||
158 | table.Columns.Add("ProductComponentItemsExists", typeof(bool)); | ||
159 | table.Columns.Add("ProductComponentItemsDbVersion", typeof(string)); | ||
160 | table.Columns.Add("ProductComponentItemsInstalledVersion", typeof(string)); | ||
161 | table.Columns.Add("ProductComponentItemsInstalledMatch", typeof(bool)); | ||
162 | try | ||
163 | { | ||
164 | IntPtr hWnd = IntPtr.Zero; | ||
165 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
166 | lock(syncRoot) // Only one Installer session can be active at a time | ||
167 | { | ||
168 | using(Session session = Installer.OpenProduct(productCode)) | ||
169 | { | ||
170 | session.DoAction("CostInitialize"); | ||
171 | session.DoAction("FileCost"); | ||
172 | session.DoAction("CostFinalize"); | ||
173 | |||
174 | foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, false)) | ||
175 | { | ||
176 | table.Rows.Add(row); | ||
177 | } | ||
178 | foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, false)) | ||
179 | { | ||
180 | table.Rows.Add(row); | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | return new DataView(table, "", "ProductComponentItemsPath ASC", DataViewRowState.CurrentRows); | ||
185 | } | ||
186 | catch(InstallerException) { } | ||
187 | return null; | ||
188 | } | ||
189 | |||
190 | private object[][] GetComponentFilesRows(string productCode, string componentCode, Session session, bool includeComponent) | ||
191 | { | ||
192 | ArrayList rows = new ArrayList(); | ||
193 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; | ||
194 | |||
195 | string componentKey = (string) session.Database.ExecuteScalar( | ||
196 | "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode); | ||
197 | if(componentKey == null) return null; | ||
198 | int attributes = Convert.ToInt32(session.Database.ExecuteScalar( | ||
199 | "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey)); | ||
200 | bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0; | ||
201 | if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath); | ||
202 | string keyPath = (string) session.Database.ExecuteScalar( | ||
203 | "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey); | ||
204 | |||
205 | using (View view = session.Database.OpenView("SELECT `File`, `FileName`, `Version`, `Language`, " + | ||
206 | "`Attributes` FROM `File` WHERE `Component_` = '{0}'", componentKey)) | ||
207 | { | ||
208 | view.Execute(); | ||
209 | |||
210 | foreach (Record rec in view) using (rec) | ||
211 | { | ||
212 | string fileKey = (string) rec["File"]; | ||
213 | bool isKey = !registryKeyPath && keyPath == fileKey; | ||
214 | |||
215 | string dbVersion = (string) rec["Version"]; | ||
216 | bool versionedFile = dbVersion.Length != 0; | ||
217 | if(versionedFile) | ||
218 | { | ||
219 | string language = (string) rec["Language"]; | ||
220 | if(language.Length > 0) | ||
221 | { | ||
222 | dbVersion = dbVersion + " (" + language + ")"; | ||
223 | } | ||
224 | } | ||
225 | else if(session.Database.Tables.Contains("MsiFileHash")) | ||
226 | { | ||
227 | IList<int> hash = session.Database.ExecuteIntegerQuery("SELECT `HashPart1`, `HashPart2`, " + | ||
228 | "`HashPart3`, `HashPart4` FROM `MsiFileHash` WHERE `File_` = '{0}'", fileKey); | ||
229 | if(hash != null && hash.Count == 4) | ||
230 | { | ||
231 | dbVersion = this.GetFileHashString(hash); | ||
232 | } | ||
233 | } | ||
234 | |||
235 | string filePath = GetLongFileName((string) rec["FileName"]); | ||
236 | bool exists = false; | ||
237 | bool installedMatch = false; | ||
238 | string installedVersion = ""; | ||
239 | if(!registryKeyPath && componentPath.Length > 0) | ||
240 | { | ||
241 | filePath = Path.Combine(componentPath, filePath); | ||
242 | |||
243 | if(File.Exists(filePath)) | ||
244 | { | ||
245 | exists = true; | ||
246 | if(versionedFile) | ||
247 | { | ||
248 | installedVersion = Installer.GetFileVersion(filePath); | ||
249 | string language = Installer.GetFileLanguage(filePath); | ||
250 | if(language.Length > 0) | ||
251 | { | ||
252 | installedVersion = installedVersion + " (" + language + ")"; | ||
253 | } | ||
254 | } | ||
255 | else | ||
256 | { | ||
257 | int[] hash = new int[4]; | ||
258 | Installer.GetFileHash(filePath, hash); | ||
259 | installedVersion = this.GetFileHashString(hash); | ||
260 | } | ||
261 | installedMatch = installedVersion == dbVersion; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | object[] row; | ||
266 | if(includeComponent) row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch, componentCode }; | ||
267 | else row = new object[] { isKey, fileKey, filePath, exists, dbVersion, installedVersion, installedMatch }; | ||
268 | rows.Add(row); | ||
269 | } | ||
270 | } | ||
271 | |||
272 | return (object[][]) rows.ToArray(typeof(object[])); | ||
273 | } | ||
274 | |||
275 | private string GetLongFileName(string fileName) | ||
276 | { | ||
277 | string[] fileNames = fileName.Split('|'); | ||
278 | return fileNames.Length == 1? fileNames[0] : fileNames[1]; | ||
279 | } | ||
280 | |||
281 | private string GetFileHashString(IList<int> hash) | ||
282 | { | ||
283 | return String.Format("{0:X8}{1:X8}{2:X8}{3:X8}", (uint) hash[0], (uint) hash[1], (uint) hash[2], (uint) hash[3]); | ||
284 | } | ||
285 | |||
286 | private object[][] GetComponentRegistryRows(string productCode, string componentCode, Session session, bool includeComponent) | ||
287 | { | ||
288 | ArrayList rows = new ArrayList(); | ||
289 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; | ||
290 | |||
291 | string componentKey = (string) session.Database.ExecuteScalar( | ||
292 | "SELECT `Component` FROM `Component` WHERE `ComponentId` = '{0}'", componentCode); | ||
293 | if(componentKey == null) return null; | ||
294 | int attributes = Convert.ToInt32(session.Database.ExecuteScalar( | ||
295 | "SELECT `Attributes` FROM `Component` WHERE `Component` = '{0}'", componentKey)); | ||
296 | bool registryKeyPath = (attributes & (int) ComponentAttributes.RegistryKeyPath) != 0; | ||
297 | if(!registryKeyPath && componentPath.Length > 0) componentPath = Path.GetDirectoryName(componentPath); | ||
298 | string keyPath = (string) session.Database.ExecuteScalar( | ||
299 | "SELECT `KeyPath` FROM `Component` WHERE `Component` = '{0}'", componentKey); | ||
300 | |||
301 | using (View view = session.Database.OpenView("SELECT `Registry`, `Root`, `Key`, `Name`, " + | ||
302 | "`Value` FROM `Registry` WHERE `Component_` = '{0}'", componentKey)) | ||
303 | { | ||
304 | view.Execute(); | ||
305 | |||
306 | foreach (Record rec in view) using (rec) | ||
307 | { | ||
308 | string regName = (string) rec["Name"]; | ||
309 | if(regName == "-") continue; // Don't list deleted keys | ||
310 | |||
311 | string regTableKey = (string) rec["Registry"]; | ||
312 | bool isKey = registryKeyPath && keyPath == regTableKey; | ||
313 | string regPath = this.GetRegistryPath(session, (RegistryRoot) Convert.ToInt32(rec["Root"]), | ||
314 | (string) rec["Key"], (string) rec["Name"]); | ||
315 | |||
316 | string dbValue; | ||
317 | using(Record formatRec = new Record(0)) | ||
318 | { | ||
319 | formatRec[0] = rec["Value"]; | ||
320 | dbValue = session.FormatRecord(formatRec); | ||
321 | } | ||
322 | |||
323 | string installedValue = this.GetRegistryValue(regPath); | ||
324 | bool exists = installedValue != null; | ||
325 | if(!exists) installedValue = ""; | ||
326 | bool match = installedValue == dbValue; | ||
327 | |||
328 | object[] row; | ||
329 | if(includeComponent) row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match, componentCode }; | ||
330 | else row = new object[] { isKey, regTableKey, regPath, exists, dbValue, installedValue, match }; | ||
331 | rows.Add(row); | ||
332 | } | ||
333 | } | ||
334 | |||
335 | return (object[][]) rows.ToArray(typeof(object[])); | ||
336 | } | ||
337 | |||
338 | private string GetRegistryPath(Session session, RegistryRoot root, string key, string name) | ||
339 | { | ||
340 | bool allUsers = session.EvaluateCondition("ALLUSERS = 1", true); | ||
341 | string rootName = "????"; | ||
342 | switch(root) | ||
343 | { | ||
344 | case RegistryRoot.LocalMachine : rootName = "HKLM"; break; | ||
345 | case RegistryRoot.CurrentUser : rootName = "HKCU"; break; | ||
346 | case RegistryRoot.Users : rootName = "HKU"; break; | ||
347 | case RegistryRoot.UserOrMachine: rootName = (allUsers ? "HKLM" : "HKCU"); break; | ||
348 | case RegistryRoot.ClassesRoot : rootName = (allUsers ? @"HKLM\Software\Classes" : @"HKCU\Software\Classes"); break; | ||
349 | // TODO: Technically, RegistryRoot.ClassesRoot should be under HKLM on NT4. | ||
350 | } | ||
351 | if(name.Length == 0) name = "(Default)"; | ||
352 | if(name == "+" || name == "*") name = ""; | ||
353 | else name = " : " + name; | ||
354 | using(Record formatRec = new Record(0)) | ||
355 | { | ||
356 | formatRec[0] = String.Format(@"{0}\{1}{2}", rootName, key, name); | ||
357 | return session.FormatRecord(formatRec); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | private string GetRegistryValue(string regPath) | ||
362 | { | ||
363 | string valueName = null; | ||
364 | int iColon = regPath.IndexOf(" : ", StringComparison.Ordinal) + 1; | ||
365 | if(iColon > 0) | ||
366 | { | ||
367 | valueName = regPath.Substring(iColon + 2); | ||
368 | regPath = regPath.Substring(0, iColon - 1); | ||
369 | } | ||
370 | if(valueName == "(Default)") valueName = ""; | ||
371 | |||
372 | RegistryKey root; | ||
373 | if(regPath.StartsWith(@"HKLM\", StringComparison.Ordinal)) | ||
374 | { | ||
375 | root = Registry.LocalMachine; | ||
376 | regPath = regPath.Substring(5); | ||
377 | } | ||
378 | else if(regPath.StartsWith(@"HKCU\", StringComparison.Ordinal)) | ||
379 | { | ||
380 | root = Registry.CurrentUser; | ||
381 | regPath = regPath.Substring(5); | ||
382 | } | ||
383 | else if(regPath.StartsWith(@"HKU\", StringComparison.Ordinal)) | ||
384 | { | ||
385 | root = Registry.Users; | ||
386 | regPath = regPath.Substring(4); | ||
387 | } | ||
388 | else return null; | ||
389 | |||
390 | using(RegistryKey regKey = root.OpenSubKey(regPath)) | ||
391 | { | ||
392 | if(regKey != null) | ||
393 | { | ||
394 | if(valueName == null) | ||
395 | { | ||
396 | // Just checking for the existence of the key. | ||
397 | return ""; | ||
398 | } | ||
399 | object value = regKey.GetValue(valueName); | ||
400 | if(value is string[]) | ||
401 | { | ||
402 | value = String.Join("[~]", (string[]) value); | ||
403 | } | ||
404 | else if(value is int) | ||
405 | { | ||
406 | value = "#" + value.ToString(); | ||
407 | } | ||
408 | else if(value is byte[]) | ||
409 | { | ||
410 | byte[] valueBytes = (byte[]) value; | ||
411 | StringBuilder byteString = new StringBuilder("#x"); | ||
412 | for(int i = 0; i < valueBytes.Length; i++) | ||
413 | { | ||
414 | byteString.Append(valueBytes[i].ToString("x2")); | ||
415 | } | ||
416 | value = byteString.ToString(); | ||
417 | } | ||
418 | return (value != null ? value.ToString() : null); | ||
419 | } | ||
420 | } | ||
421 | return null; | ||
422 | } | ||
423 | |||
424 | public DataView GetProductFilesData(string productCode) | ||
425 | { | ||
426 | DataTable table = new DataTable("ProductFiles"); | ||
427 | table.Locale = CultureInfo.InvariantCulture; | ||
428 | table.Columns.Add("ProductFilesIsKey", typeof(bool)); | ||
429 | table.Columns.Add("ProductFilesKey", typeof(string)); | ||
430 | table.Columns.Add("ProductFilesPath", typeof(string)); | ||
431 | table.Columns.Add("ProductFilesExists", typeof(bool)); | ||
432 | table.Columns.Add("ProductFilesDbVersion", typeof(string)); | ||
433 | table.Columns.Add("ProductFilesInstalledVersion", typeof(string)); | ||
434 | table.Columns.Add("ProductFilesInstalledMatch", typeof(bool)); | ||
435 | table.Columns.Add("ProductFilesComponentID", typeof(string)); | ||
436 | try | ||
437 | { | ||
438 | IntPtr hWnd = IntPtr.Zero; | ||
439 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
440 | lock(syncRoot) // Only one Installer session can be active at a time | ||
441 | { | ||
442 | using(Session session = Installer.OpenProduct(productCode)) | ||
443 | { | ||
444 | session.DoAction("CostInitialize"); | ||
445 | session.DoAction("FileCost"); | ||
446 | session.DoAction("CostFinalize"); | ||
447 | |||
448 | foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`")) | ||
449 | { | ||
450 | foreach(object[] row in this.GetComponentFilesRows(productCode, componentCode, session, true)) | ||
451 | { | ||
452 | table.Rows.Add(row); | ||
453 | } | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | return new DataView(table, "", "ProductFilesPath ASC", DataViewRowState.CurrentRows); | ||
458 | } | ||
459 | catch(InstallerException) { } | ||
460 | return null; | ||
461 | } | ||
462 | |||
463 | public DataView GetProductRegistryData(string productCode) | ||
464 | { | ||
465 | DataTable table = new DataTable("ProductRegistry"); | ||
466 | table.Locale = CultureInfo.InvariantCulture; | ||
467 | table.Columns.Add("ProductRegistryIsKey", typeof(bool)); | ||
468 | table.Columns.Add("ProductRegistryKey", typeof(string)); | ||
469 | table.Columns.Add("ProductRegistryPath", typeof(string)); | ||
470 | table.Columns.Add("ProductRegistryExists", typeof(bool)); | ||
471 | table.Columns.Add("ProductRegistryDbVersion", typeof(string)); | ||
472 | table.Columns.Add("ProductRegistryInstalledVersion", typeof(string)); | ||
473 | table.Columns.Add("ProductRegistryInstalledMatch", typeof(bool)); | ||
474 | table.Columns.Add("ProductRegistryComponentID", typeof(string)); | ||
475 | try | ||
476 | { | ||
477 | IntPtr hWnd = IntPtr.Zero; | ||
478 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
479 | lock(syncRoot) // Only one Installer session can be active at a time | ||
480 | { | ||
481 | using(Session session = Installer.OpenProduct(productCode)) | ||
482 | { | ||
483 | session.DoAction("CostInitialize"); | ||
484 | session.DoAction("FileCost"); | ||
485 | session.DoAction("CostFinalize"); | ||
486 | |||
487 | foreach(string componentCode in session.Database.ExecuteStringQuery("SELECT `ComponentId` FROM `Component`")) | ||
488 | { | ||
489 | foreach(object[] row in this.GetComponentRegistryRows(productCode, componentCode, session, true)) | ||
490 | { | ||
491 | table.Rows.Add(row); | ||
492 | } | ||
493 | } | ||
494 | } | ||
495 | } | ||
496 | return new DataView(table, "", "ProductRegistryPath ASC", DataViewRowState.CurrentRows); | ||
497 | } | ||
498 | catch(InstallerException) { } | ||
499 | return null; | ||
500 | } | ||
501 | |||
502 | public DataView GetComponentProductsData(string componentCode) | ||
503 | { | ||
504 | DataTable table = new DataTable("ComponentProducts"); | ||
505 | table.Locale = CultureInfo.InvariantCulture; | ||
506 | table.Columns.Add("ComponentProductsProductName", typeof(string)); | ||
507 | table.Columns.Add("ComponentProductsProductCode", typeof(string)); | ||
508 | table.Columns.Add("ComponentProductsComponentPath", typeof(string)); | ||
509 | |||
510 | if(this.componentProductsMap != null) | ||
511 | { | ||
512 | ArrayList componentProducts = (ArrayList) this.componentProductsMap[componentCode]; | ||
513 | foreach(string productCode in componentProducts) | ||
514 | { | ||
515 | string productName = MsiUtils.GetProductName(productCode); | ||
516 | string componentPath = new ComponentInstallation(componentCode, productCode).Path; | ||
517 | table.Rows.Add(new object[] { productName, productCode, componentPath }); | ||
518 | } | ||
519 | return new DataView(table, "", "ComponentProductsProductName ASC", DataViewRowState.CurrentRows); | ||
520 | } | ||
521 | return null; | ||
522 | } | ||
523 | |||
524 | public DataView GetProductComponentsData(string productCode) | ||
525 | { | ||
526 | DataTable table = new DataTable("ProductComponents"); | ||
527 | table.Locale = CultureInfo.InvariantCulture; | ||
528 | table.Columns.Add("ProductComponentsComponentName", typeof(string)); | ||
529 | table.Columns.Add("ProductComponentsComponentID", typeof(string)); | ||
530 | table.Columns.Add("ProductComponentsInstallState", typeof(string)); | ||
531 | |||
532 | try | ||
533 | { | ||
534 | IntPtr hWnd = IntPtr.Zero; | ||
535 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
536 | lock(syncRoot) // Only one Installer session can be active at a time | ||
537 | { | ||
538 | using(Session session = Installer.OpenProduct(productCode)) | ||
539 | { | ||
540 | session.DoAction("CostInitialize"); | ||
541 | session.DoAction("FileCost"); | ||
542 | session.DoAction("CostFinalize"); | ||
543 | |||
544 | IList<string> componentsAndIds = session.Database.ExecuteStringQuery( | ||
545 | "SELECT `Component`, `ComponentId` FROM `Component`"); | ||
546 | |||
547 | for (int i = 0; i < componentsAndIds.Count; i += 2) | ||
548 | { | ||
549 | if(componentsAndIds[i+1] == "Temporary Id") continue; | ||
550 | InstallState compState = session.Components[componentsAndIds[i]].CurrentState; | ||
551 | table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1], | ||
552 | (compState == InstallState.Advertised ? "Advertised" : compState.ToString())}); | ||
553 | } | ||
554 | } | ||
555 | } | ||
556 | return new DataView(table, "", "ProductComponentsComponentName ASC", DataViewRowState.CurrentRows); | ||
557 | } | ||
558 | catch(InstallerException) { } | ||
559 | return null; | ||
560 | } | ||
561 | |||
562 | public DataView GetFeatureComponentsData(string productCode, string feature) | ||
563 | { | ||
564 | DataTable table = new DataTable("ProductFeatureComponents"); | ||
565 | table.Locale = CultureInfo.InvariantCulture; | ||
566 | table.Columns.Add("ProductFeatureComponentsComponentName", typeof(string)); | ||
567 | table.Columns.Add("ProductFeatureComponentsComponentID", typeof(string)); | ||
568 | |||
569 | try | ||
570 | { | ||
571 | IntPtr hWnd = IntPtr.Zero; | ||
572 | Installer.SetInternalUI(InstallUIOptions.Silent, ref hWnd); | ||
573 | lock(syncRoot) // Only one Installer session can be active at a time | ||
574 | { | ||
575 | using(Session session = Installer.OpenProduct(productCode)) | ||
576 | { | ||
577 | IList<string> componentsAndIds = session.Database.ExecuteStringQuery( | ||
578 | "SELECT `FeatureComponents`.`Component_`, " + | ||
579 | "`Component`.`ComponentId` FROM `FeatureComponents`, `Component` " + | ||
580 | "WHERE `FeatureComponents`.`Component_` = `Component`.`Component` " + | ||
581 | "AND `FeatureComponents`.`Feature_` = '{0}'", feature); | ||
582 | for (int i = 0; i < componentsAndIds.Count; i += 2) | ||
583 | { | ||
584 | table.Rows.Add(new object[] { componentsAndIds[i], componentsAndIds[i+1] }); | ||
585 | } | ||
586 | } | ||
587 | } | ||
588 | return new DataView(table, "", "ProductFeatureComponentsComponentName ASC", DataViewRowState.CurrentRows); | ||
589 | } | ||
590 | catch(InstallerException) { } | ||
591 | return null; | ||
592 | } | ||
593 | |||
594 | public string GetLink(string nodePath, DataRow row) | ||
595 | { | ||
596 | string[] path = nodePath.Split('\\'); | ||
597 | |||
598 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Components") | ||
599 | { | ||
600 | string component = (string) row["ProductComponentsComponentID"]; | ||
601 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); | ||
602 | } | ||
603 | else if(path.Length == 4 && path[0] == "Products" && path[2] == "Features") | ||
604 | { | ||
605 | string component = (string) row["ProductFeatureComponentsComponentID"]; | ||
606 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); | ||
607 | } | ||
608 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Files") | ||
609 | { | ||
610 | string component = (string) row["ProductFilesComponentID"]; | ||
611 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); | ||
612 | } | ||
613 | else if(path.Length == 3 && path[0] == "Products" && path[2] == "Registry") | ||
614 | { | ||
615 | string component = (string) row["ProductRegistryComponentID"]; | ||
616 | return String.Format(@"Products\{0}\Components\{1}", path[1], component); | ||
617 | } | ||
618 | else if(path.Length == 5 && path[0] == "Products" && path[2] == "Components" && path[4] == "Sharing") | ||
619 | { | ||
620 | string product = (string) row["ComponentProductsProductCode"]; | ||
621 | return String.Format(@"Products\{0}\Components\{1}", MsiUtils.GetProductName(product), path[3]); | ||
622 | } | ||
623 | return null; | ||
624 | } | ||
625 | } | ||
626 | } | ||
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 | |||
3 | using System; | ||
4 | using System.Collections; | ||
5 | using WixToolset.Dtf.WindowsInstaller; | ||
6 | |||
7 | |||
8 | namespace WixToolset.Dtf.Samples.Inventory | ||
9 | { | ||
10 | public class MsiUtils | ||
11 | { | ||
12 | private static Hashtable productCodesToNames = new Hashtable(); | ||
13 | private static Hashtable productNamesToCodes = new Hashtable(); | ||
14 | |||
15 | public static string GetProductName(string productCode) | ||
16 | { | ||
17 | string productName = (string) productCodesToNames[productCode]; | ||
18 | if(productName == null) | ||
19 | { | ||
20 | productName = new ProductInstallation(productCode).ProductName; | ||
21 | productName = productName.Replace('\\', ' '); | ||
22 | if(productNamesToCodes.Contains(productName)) | ||
23 | { | ||
24 | string modifiedProductName = null; | ||
25 | for(int i = 2; i < Int32.MaxValue; i++) | ||
26 | { | ||
27 | modifiedProductName = productName + " [" + i + "]"; | ||
28 | if(!productNamesToCodes.Contains(modifiedProductName)) break; | ||
29 | } | ||
30 | productName = modifiedProductName; | ||
31 | } | ||
32 | productCodesToNames[productCode] = productName; | ||
33 | productNamesToCodes[productName] = productCode; | ||
34 | } | ||
35 | return productName; | ||
36 | } | ||
37 | |||
38 | // Assumes GetProductName() has already been called for this product. | ||
39 | public static string GetProductCode(string productName) | ||
40 | { | ||
41 | return (string) productNamesToCodes[productName]; | ||
42 | } | ||
43 | |||
44 | private MsiUtils() { } | ||
45 | } | ||
46 | } | ||
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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Security; | ||
6 | using System.Data; | ||
7 | using System.Collections; | ||
8 | using System.Globalization; | ||
9 | using System.Windows.Forms; | ||
10 | using WixToolset.Dtf.WindowsInstaller; | ||
11 | |||
12 | namespace WixToolset.Dtf.Samples.Inventory | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Provides inventory data about patches installed on the system. | ||
16 | /// </summary> | ||
17 | public class PatchesInventory : IInventoryDataProvider | ||
18 | { | ||
19 | public PatchesInventory() | ||
20 | { | ||
21 | } | ||
22 | |||
23 | public string Description | ||
24 | { | ||
25 | get { return "Installed patches"; } | ||
26 | } | ||
27 | |||
28 | public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback) | ||
29 | { | ||
30 | ArrayList nodes = new ArrayList(); | ||
31 | statusCallback(nodes.Count, @"Products\...\Patches"); | ||
32 | foreach (ProductInstallation product in ProductInstallation.AllProducts) | ||
33 | { | ||
34 | string productName = MsiUtils.GetProductName(product.ProductCode); | ||
35 | |||
36 | bool addedRoot = false; | ||
37 | foreach (PatchInstallation productPatch in PatchInstallation.GetPatches(null, product.ProductCode, null, UserContexts.All, PatchStates.Applied)) | ||
38 | { | ||
39 | if (!addedRoot) nodes.Add(String.Format(@"Products\{0}\Patches", productName)); | ||
40 | nodes.Add(String.Format(@"Products\{0}\Patches\{1}", productName, productPatch.PatchCode)); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | statusCallback(nodes.Count, "Patches"); | ||
45 | |||
46 | string[] allPatches = GetAllPatchesList(); | ||
47 | if(allPatches.Length > 0) | ||
48 | { | ||
49 | nodes.Add("Patches"); | ||
50 | foreach(string patchCode in allPatches) | ||
51 | { | ||
52 | nodes.Add(String.Format(@"Patches\{0}", patchCode)); | ||
53 | nodes.Add(String.Format(@"Patches\{0}\Patched Products", patchCode)); | ||
54 | } | ||
55 | statusCallback(nodes.Count, String.Empty); | ||
56 | } | ||
57 | return (string[]) nodes.ToArray(typeof(string)); | ||
58 | } | ||
59 | |||
60 | public bool IsNodeSearchable(string searchRoot, string searchNode) | ||
61 | { | ||
62 | return true; | ||
63 | } | ||
64 | |||
65 | public DataView GetData(string nodePath) | ||
66 | { | ||
67 | string[] path = nodePath.Split('\\'); | ||
68 | |||
69 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches") | ||
70 | { | ||
71 | return this.GetProductPatchData(path[1]); | ||
72 | } | ||
73 | else if(path.Length == 4 && path[0] == "Products" && path[2] == "Patches") | ||
74 | { | ||
75 | return this.GetPatchData(path[3]); | ||
76 | } | ||
77 | else if(path.Length == 1 && path[0] == "Patches") | ||
78 | { | ||
79 | return this.GetAllPatchesData(); | ||
80 | } | ||
81 | else if(path.Length == 2 && path[0] == "Patches") | ||
82 | { | ||
83 | return this.GetPatchData(path[1]); | ||
84 | } | ||
85 | else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products") | ||
86 | { | ||
87 | return this.GetPatchTargetData(path[1]); | ||
88 | } | ||
89 | return null; | ||
90 | } | ||
91 | |||
92 | private string[] GetAllPatchesList() | ||
93 | { | ||
94 | ArrayList patchList = new ArrayList(); | ||
95 | foreach(PatchInstallation patch in PatchInstallation.AllPatches) | ||
96 | { | ||
97 | if(!patchList.Contains(patch.PatchCode)) | ||
98 | { | ||
99 | patchList.Add(patch.PatchCode); | ||
100 | } | ||
101 | } | ||
102 | string[] patchArray = (string[]) patchList.ToArray(typeof(string)); | ||
103 | Array.Sort(patchArray, 0, patchArray.Length, StringComparer.Ordinal); | ||
104 | return patchArray; | ||
105 | } | ||
106 | |||
107 | private DataView GetAllPatchesData() | ||
108 | { | ||
109 | DataTable table = new DataTable("Patches"); | ||
110 | table.Locale = CultureInfo.InvariantCulture; | ||
111 | table.Columns.Add("PatchesPatchCode", typeof(string)); | ||
112 | |||
113 | foreach(string patchCode in GetAllPatchesList()) | ||
114 | { | ||
115 | table.Rows.Add(new object[] { patchCode }); | ||
116 | } | ||
117 | return new DataView(table, "", "PatchesPatchCode ASC", DataViewRowState.CurrentRows); | ||
118 | } | ||
119 | |||
120 | private DataView GetProductPatchData(string productCode) | ||
121 | { | ||
122 | DataTable table = new DataTable("ProductPatches"); | ||
123 | table.Locale = CultureInfo.InvariantCulture; | ||
124 | table.Columns.Add("ProductPatchesPatchCode", typeof(string)); | ||
125 | |||
126 | foreach(PatchInstallation patch in PatchInstallation.GetPatches(null, productCode, null, UserContexts.All, PatchStates.Applied)) | ||
127 | { | ||
128 | table.Rows.Add(new object[] { patch.PatchCode }); | ||
129 | } | ||
130 | return new DataView(table, "", "ProductPatchesPatchCode ASC", DataViewRowState.CurrentRows); | ||
131 | } | ||
132 | |||
133 | private DataView GetPatchData(string patchCode) | ||
134 | { | ||
135 | DataTable table = new DataTable("PatchProperties"); | ||
136 | table.Locale = CultureInfo.InvariantCulture; | ||
137 | table.Columns.Add("PatchPropertiesProperty", typeof(string)); | ||
138 | table.Columns.Add("PatchPropertiesValue", typeof(string)); | ||
139 | |||
140 | table.Rows.Add(new object[] { "PatchCode", patchCode }); | ||
141 | |||
142 | PatchInstallation patch = new PatchInstallation(patchCode, null); | ||
143 | |||
144 | string localPackage = null; | ||
145 | foreach(string property in new string[] | ||
146 | { | ||
147 | "InstallDate", | ||
148 | "LocalPackage", | ||
149 | "State", | ||
150 | "Transforms", | ||
151 | "Uninstallable", | ||
152 | }) | ||
153 | { | ||
154 | try | ||
155 | { | ||
156 | string value = patch[property]; | ||
157 | table.Rows.Add(new object[] { property, (value != null ? value : "") }); | ||
158 | if(property == "LocalPackage") localPackage = value; | ||
159 | } | ||
160 | catch(InstallerException iex) | ||
161 | { | ||
162 | table.Rows.Add(new object[] { property, iex.Message }); | ||
163 | } | ||
164 | catch(ArgumentException) { } | ||
165 | } | ||
166 | |||
167 | if(localPackage != null) | ||
168 | { | ||
169 | try | ||
170 | { | ||
171 | using(SummaryInfo patchSummaryInfo = new SummaryInfo(localPackage, false)) | ||
172 | { | ||
173 | table.Rows.Add(new object[] { "Title", patchSummaryInfo.Title }); | ||
174 | table.Rows.Add(new object[] { "Subject", patchSummaryInfo.Subject }); | ||
175 | table.Rows.Add(new object[] { "Author", patchSummaryInfo.Author }); | ||
176 | table.Rows.Add(new object[] { "Comments", patchSummaryInfo.Comments }); | ||
177 | table.Rows.Add(new object[] { "TargetProductCodes", patchSummaryInfo.Template }); | ||
178 | string obsoletedPatchCodes = patchSummaryInfo.RevisionNumber.Substring(patchSummaryInfo.RevisionNumber.IndexOf('}') + 1); | ||
179 | table.Rows.Add(new object[] { "ObsoletedPatchCodes", obsoletedPatchCodes }); | ||
180 | table.Rows.Add(new object[] { "TransformNames", patchSummaryInfo.LastSavedBy }); | ||
181 | } | ||
182 | } | ||
183 | catch(InstallerException) { } | ||
184 | catch(IOException) { } | ||
185 | catch(SecurityException) { } | ||
186 | } | ||
187 | return new DataView(table, "", "PatchPropertiesProperty ASC", DataViewRowState.CurrentRows); | ||
188 | } | ||
189 | |||
190 | private DataView GetPatchTargetData(string patchCode) | ||
191 | { | ||
192 | DataTable table = new DataTable("PatchTargets"); | ||
193 | table.Locale = CultureInfo.InvariantCulture; | ||
194 | table.Columns.Add("PatchTargetsProductName", typeof(string)); | ||
195 | table.Columns.Add("PatchTargetsProductCode", typeof(string)); | ||
196 | |||
197 | foreach (PatchInstallation patch in PatchInstallation.GetPatches(patchCode, null, null, UserContexts.All, PatchStates.Applied)) | ||
198 | { | ||
199 | if(patch.PatchCode == patchCode) | ||
200 | { | ||
201 | string productName = MsiUtils.GetProductName(patch.ProductCode); | ||
202 | table.Rows.Add(new object[] { productName, patch.ProductCode }); | ||
203 | } | ||
204 | } | ||
205 | return new DataView(table, "", "PatchTargetsProductName ASC", DataViewRowState.CurrentRows); | ||
206 | } | ||
207 | |||
208 | public string GetLink(string nodePath, DataRow row) | ||
209 | { | ||
210 | string[] path = nodePath.Split('\\'); | ||
211 | |||
212 | if(path.Length == 3 && path[0] == "Products" && path[2] == "Patches") | ||
213 | { | ||
214 | return String.Format(@"Patches\{0}", row["ProductPatchesPatchCode"]); | ||
215 | } | ||
216 | else if(path.Length == 1 && path[0] == "Patches") | ||
217 | { | ||
218 | return String.Format(@"Patches\{0}", row["PatchesPatchCode"]); | ||
219 | } | ||
220 | else if(path.Length == 3 && path[0] == "Patches" && path[2] == "Patched Products") | ||
221 | { | ||
222 | return String.Format(@"Products\{0}", MsiUtils.GetProductCode((string) row["PatchTargetsProductCode"])); | ||
223 | } | ||
224 | return null; | ||
225 | } | ||
226 | } | ||
227 | } | ||
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 | |||
3 | using System; | ||
4 | using System.Data; | ||
5 | using System.Globalization; | ||
6 | using System.Collections; | ||
7 | using System.Windows.Forms; | ||
8 | using WixToolset.Dtf.WindowsInstaller; | ||
9 | |||
10 | namespace WixToolset.Dtf.Samples.Inventory | ||
11 | { | ||
12 | /// <summary> | ||
13 | /// Provides inventory data about products installed or advertised on the system. | ||
14 | /// </summary> | ||
15 | public class ProductsInventory : IInventoryDataProvider | ||
16 | { | ||
17 | public ProductsInventory() | ||
18 | { | ||
19 | } | ||
20 | |||
21 | public string Description | ||
22 | { | ||
23 | get { return "Installed products"; } | ||
24 | } | ||
25 | |||
26 | public string[] GetNodes(InventoryDataLoadStatusCallback statusCallback) | ||
27 | { | ||
28 | statusCallback(0, "Products"); | ||
29 | ArrayList nodes = new ArrayList(); | ||
30 | nodes.Add("Products"); | ||
31 | foreach(ProductInstallation product in ProductInstallation.AllProducts) | ||
32 | { | ||
33 | nodes.Add("Products\\" + MsiUtils.GetProductName(product.ProductCode)); | ||
34 | } | ||
35 | statusCallback(nodes.Count, String.Empty); | ||
36 | return (string[]) nodes.ToArray(typeof(string)); | ||
37 | } | ||
38 | |||
39 | public bool IsNodeSearchable(string searchRoot, string searchNode) | ||
40 | { | ||
41 | return true; | ||
42 | } | ||
43 | |||
44 | public DataView GetData(string nodePath) | ||
45 | { | ||
46 | string[] path = nodePath.Split('\\'); | ||
47 | |||
48 | if(path.Length == 1 && path[0] == "Products") | ||
49 | { | ||
50 | return this.GetAllProductsData(); | ||
51 | } | ||
52 | else if(path.Length == 2 && path[0] == "Products") | ||
53 | { | ||
54 | return this.GetProductData(MsiUtils.GetProductCode(path[1])); | ||
55 | } | ||
56 | return null; | ||
57 | } | ||
58 | |||
59 | private DataView GetAllProductsData() | ||
60 | { | ||
61 | DataTable table = new DataTable("Products"); | ||
62 | table.Locale = CultureInfo.InvariantCulture; | ||
63 | table.Columns.Add("ProductsProductName", typeof(string)); | ||
64 | table.Columns.Add("ProductsProductCode", typeof(string)); | ||
65 | |||
66 | foreach (ProductInstallation product in ProductInstallation.AllProducts) | ||
67 | { | ||
68 | string productName = MsiUtils.GetProductName(product.ProductCode); | ||
69 | table.Rows.Add(new object[] { productName, product.ProductCode }); | ||
70 | } | ||
71 | return new DataView(table, "", "ProductsProductName ASC", DataViewRowState.CurrentRows); | ||
72 | } | ||
73 | |||
74 | private DataView GetProductData(string productCode) | ||
75 | { | ||
76 | DataTable table = new DataTable("ProductProperties"); | ||
77 | table.Locale = CultureInfo.InvariantCulture; | ||
78 | table.Columns.Add("ProductPropertiesProperty", typeof(string)); | ||
79 | table.Columns.Add("ProductPropertiesValue", typeof(string)); | ||
80 | |||
81 | // Add a fake "ProductCode" install property, just for display convenience. | ||
82 | table.Rows.Add(new object[] { "ProductCode", productCode }); | ||
83 | |||
84 | ProductInstallation product = new ProductInstallation(productCode); | ||
85 | |||
86 | foreach(string property in new string[] | ||
87 | { | ||
88 | "AssignmentType", | ||
89 | "DiskPrompt", | ||
90 | "HelpLink", | ||
91 | "HelpTelephone", | ||
92 | "InstalledProductName", | ||
93 | "InstallDate", | ||
94 | "InstallLocation", | ||
95 | "InstallSource", | ||
96 | "Language", | ||
97 | "LastUsedSource", | ||
98 | "LastUsedType", | ||
99 | "LocalPackage", | ||
100 | "MediaPackagePath", | ||
101 | "PackageCode", | ||
102 | "PackageName", | ||
103 | "ProductIcon", | ||
104 | "ProductID", | ||
105 | "ProductName", | ||
106 | "Publisher", | ||
107 | "RegCompany", | ||
108 | "RegOwner", | ||
109 | "State", | ||
110 | "transforms", | ||
111 | "Uninstallable", | ||
112 | "UrlInfoAbout", | ||
113 | "UrlUpdateInfo", | ||
114 | "Version", | ||
115 | "VersionMinor", | ||
116 | "VersionMajor", | ||
117 | "VersionString" | ||
118 | }) | ||
119 | { | ||
120 | try | ||
121 | { | ||
122 | string value = product[property]; | ||
123 | table.Rows.Add(new object[] { property, (value != null ? value : "") }); | ||
124 | } | ||
125 | catch(InstallerException iex) | ||
126 | { | ||
127 | table.Rows.Add(new object[] { property, iex.Message }); | ||
128 | } | ||
129 | catch(ArgumentException) { } | ||
130 | } | ||
131 | return new DataView(table, "", "ProductPropertiesProperty ASC", DataViewRowState.CurrentRows); | ||
132 | } | ||
133 | |||
134 | public string GetLink(string nodePath, DataRow row) | ||
135 | { | ||
136 | string[] path = nodePath.Split('\\'); | ||
137 | |||
138 | if(path.Length == 1 && path[0] == "Products") | ||
139 | { | ||
140 | return String.Format(@"Products\{0}", MsiUtils.GetProductName((string) row["ProductsProductCode"])); | ||
141 | } | ||
142 | return null; | ||
143 | } | ||
144 | } | ||
145 | } | ||
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 | |||
3 | using 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 | |||
3 | namespace WixToolset.Dtf.Samples.ManagedCA | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.IO; | ||
8 | using WixToolset.Dtf.WindowsInstaller; | ||
9 | |||
10 | public class SampleCAs | ||
11 | { | ||
12 | [CustomAction] | ||
13 | public static ActionResult SampleCA1(Session session) | ||
14 | { | ||
15 | using (Record msgRec = new Record(0)) | ||
16 | { | ||
17 | msgRec[0] = "Hello from SampleCA1!" + | ||
18 | "\r\nCLR version is v" + Environment.Version; | ||
19 | session.Message(InstallMessage.Info, msgRec); | ||
20 | session.Message(InstallMessage.User, msgRec); | ||
21 | } | ||
22 | |||
23 | session.Log("Testing summary info..."); | ||
24 | SummaryInfo summInfo = session.Database.SummaryInfo; | ||
25 | session.Log("MSI PackageCode = {0}", summInfo.RevisionNumber); | ||
26 | session.Log("MSI ModifyDate = {0}", summInfo.LastSaveTime); | ||
27 | |||
28 | string testProp = session["SampleCATest"]; | ||
29 | session.Log("Simple property test: [SampleCATest]={0}.", testProp); | ||
30 | |||
31 | session.Log("Testing subdirectory extraction..."); | ||
32 | string testFilePath = "testsub\\SampleCAs.cs"; | ||
33 | if (!File.Exists(testFilePath)) | ||
34 | { | ||
35 | session.Log("Subdirectory extraction failed. File not found: " + testFilePath); | ||
36 | return ActionResult.Failure; | ||
37 | } | ||
38 | else | ||
39 | { | ||
40 | session.Log("Found file extracted in subdirectory."); | ||
41 | } | ||
42 | |||
43 | session.Log("Testing record stream extraction..."); | ||
44 | string tempFile = null; | ||
45 | try | ||
46 | { | ||
47 | tempFile = Path.GetTempFileName(); | ||
48 | using (View binView = session.Database.OpenView( | ||
49 | "SELECT `Binary`.`Data` FROM `Binary`, `CustomAction` " + | ||
50 | "WHERE `CustomAction`.`Target` = 'SampleCA1' AND " + | ||
51 | "`CustomAction`.`Source` = `Binary`.`Name`")) | ||
52 | { | ||
53 | binView.Execute(); | ||
54 | using (Record binRec = binView.Fetch()) | ||
55 | { | ||
56 | binRec.GetStream(1, tempFile); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | session.Log("CA binary file size: {0}", new FileInfo(tempFile).Length); | ||
61 | string binFileVersion = Installer.GetFileVersion(tempFile); | ||
62 | session.Log("CA binary file version: {0}", binFileVersion); | ||
63 | } | ||
64 | finally | ||
65 | { | ||
66 | if (tempFile != null && File.Exists(tempFile)) | ||
67 | { | ||
68 | File.Delete(tempFile); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | session.Log("Testing record stream reading..."); | ||
73 | using (View binView2 = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = 'TestData'")) | ||
74 | { | ||
75 | binView2.Execute(); | ||
76 | using (Record binRec2 = binView2.Fetch()) | ||
77 | { | ||
78 | Stream stream = binRec2.GetStream("Data"); | ||
79 | string testData = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd(); | ||
80 | session.Log("Test data: " + testData); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | session.Log("Listing components"); | ||
85 | using (View compView = session.Database.OpenView( | ||
86 | "SELECT `Component` FROM `Component`")) | ||
87 | { | ||
88 | compView.Execute(); | ||
89 | foreach (Record compRec in compView) | ||
90 | { | ||
91 | using (compRec) | ||
92 | { | ||
93 | session.Log("\t{0}", compRec["Component"]); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | session.Log("Testing the ability to access an external MSI database..."); | ||
99 | string tempDbFile = Path.GetTempFileName(); | ||
100 | using (Database tempDb = new Database(tempDbFile, DatabaseOpenMode.CreateDirect)) | ||
101 | { | ||
102 | // Just create an empty database. | ||
103 | } | ||
104 | using (Database tempDb2 = new Database(tempDbFile)) | ||
105 | { | ||
106 | // See if we can open and query the database. | ||
107 | IList<string> tables = tempDb2.ExecuteStringQuery("SELECT `Name` FROM `_Tables`"); | ||
108 | session.Log("Found " + tables.Count + " tables in the newly created database."); | ||
109 | } | ||
110 | File.Delete(tempDbFile); | ||
111 | |||
112 | return ActionResult.Success; | ||
113 | } | ||
114 | |||
115 | [CustomAction("SampleCA2")] | ||
116 | public static ActionResult SampleCustomAction2(Session session) | ||
117 | { | ||
118 | using (Record msgRec = new Record(0)) | ||
119 | { | ||
120 | msgRec[0] = "Hello from SampleCA2!"; | ||
121 | session.Message(InstallMessage.Info, msgRec); | ||
122 | session.Message(InstallMessage.User, msgRec); | ||
123 | } | ||
124 | return ActionResult.UserExit; | ||
125 | } | ||
126 | } | ||
127 | } | ||
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 | |||
3 | namespace WixToolset.Dtf.Tools.MakeSfxCA | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Security; | ||
9 | using System.Text; | ||
10 | using System.Reflection; | ||
11 | using Compression; | ||
12 | using Compression.Cab; | ||
13 | using Resources; | ||
14 | using ResourceCollection = Resources.ResourceCollection; | ||
15 | |||
16 | /// <summary> | ||
17 | /// Command-line tool for building self-extracting custom action packages. | ||
18 | /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's | ||
19 | /// entry-points and file version to look like the CA module. | ||
20 | /// </summary> | ||
21 | public static class MakeSfxCA | ||
22 | { | ||
23 | private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll"; | ||
24 | |||
25 | private static TextWriter log; | ||
26 | |||
27 | /// <summary> | ||
28 | /// Prints usage text for the tool. | ||
29 | /// </summary> | ||
30 | /// <param name="w">Console text writer.</param> | ||
31 | public static void Usage(TextWriter w) | ||
32 | { | ||
33 | w.WriteLine("Deployment Tools Foundation custom action packager version {0}", | ||
34 | Assembly.GetExecutingAssembly().GetName().Version); | ||
35 | w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved."); | ||
36 | w.WriteLine(); | ||
37 | w.WriteLine("Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]"); | ||
38 | w.WriteLine(); | ||
39 | w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package."); | ||
40 | w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY); | ||
41 | w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config"); | ||
42 | } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Runs the MakeSfxCA command-line tool. | ||
46 | /// </summary> | ||
47 | /// <param name="args">Command-line arguments.</param> | ||
48 | /// <returns>0 on success, nonzero on failure.</returns> | ||
49 | public static int Main(string[] args) | ||
50 | { | ||
51 | if (args.Length < 3) | ||
52 | { | ||
53 | Usage(Console.Out); | ||
54 | return 1; | ||
55 | } | ||
56 | |||
57 | var output = args[0]; | ||
58 | var sfxDll = args[1]; | ||
59 | var inputs = new string[args.Length - 2]; | ||
60 | Array.Copy(args, 2, inputs, 0, inputs.Length); | ||
61 | |||
62 | try | ||
63 | { | ||
64 | Build(output, sfxDll, inputs, Console.Out); | ||
65 | return 0; | ||
66 | } | ||
67 | catch (ArgumentException ex) | ||
68 | { | ||
69 | Console.Error.WriteLine("Error: Invalid argument: " + ex.Message); | ||
70 | return 1; | ||
71 | } | ||
72 | catch (FileNotFoundException ex) | ||
73 | { | ||
74 | Console.Error.WriteLine("Error: Cannot find file: " + ex.Message); | ||
75 | return 1; | ||
76 | } | ||
77 | catch (Exception ex) | ||
78 | { | ||
79 | Console.Error.WriteLine("Error: Unexpected error: " + ex); | ||
80 | return 1; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Packages up all the inputs to the output location. | ||
86 | /// </summary> | ||
87 | /// <exception cref="Exception">Various exceptions are thrown | ||
88 | /// if things go wrong.</exception> | ||
89 | public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log) | ||
90 | { | ||
91 | MakeSfxCA.log = log; | ||
92 | |||
93 | if (string.IsNullOrEmpty(output)) | ||
94 | { | ||
95 | throw new ArgumentNullException("output"); | ||
96 | } | ||
97 | |||
98 | if (string.IsNullOrEmpty(sfxDll)) | ||
99 | { | ||
100 | throw new ArgumentNullException("sfxDll"); | ||
101 | } | ||
102 | |||
103 | if (inputs == null || inputs.Count == 0) | ||
104 | { | ||
105 | throw new ArgumentNullException("inputs"); | ||
106 | } | ||
107 | |||
108 | if (!File.Exists(sfxDll)) | ||
109 | { | ||
110 | throw new FileNotFoundException(sfxDll); | ||
111 | } | ||
112 | |||
113 | var customActionAssembly = inputs[0]; | ||
114 | if (!File.Exists(customActionAssembly)) | ||
115 | { | ||
116 | throw new FileNotFoundException(customActionAssembly); | ||
117 | } | ||
118 | |||
119 | inputs = MakeSfxCA.SplitList(inputs); | ||
120 | |||
121 | var inputsMap = MakeSfxCA.GetPackFileMap(inputs); | ||
122 | |||
123 | var foundWIAssembly = false; | ||
124 | foreach (var input in inputsMap.Keys) | ||
125 | { | ||
126 | if (string.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY, | ||
127 | StringComparison.OrdinalIgnoreCase) == 0) | ||
128 | { | ||
129 | foundWIAssembly = true; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | if (!foundWIAssembly) | ||
134 | { | ||
135 | throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY + | ||
136 | " must be included in the list of support files. " + | ||
137 | "If using the MSBuild targets, make sure the assembly reference " + | ||
138 | "has the Private (Copy Local) flag set."); | ||
139 | } | ||
140 | |||
141 | MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly)); | ||
142 | |||
143 | var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly); | ||
144 | var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly); | ||
145 | |||
146 | if (entryPoints.Count == 0 && uiClass == null) | ||
147 | { | ||
148 | throw new ArgumentException( | ||
149 | "No CA or UI entry points found in module: " + customActionAssembly); | ||
150 | } | ||
151 | else if (entryPoints.Count > 0 && uiClass != null) | ||
152 | { | ||
153 | throw new NotSupportedException( | ||
154 | "CA and UI entry points cannot be in the same assembly: " + customActionAssembly); | ||
155 | } | ||
156 | |||
157 | var dir = Path.GetDirectoryName(output); | ||
158 | if (dir.Length > 0 && !Directory.Exists(dir)) | ||
159 | { | ||
160 | Directory.CreateDirectory(dir); | ||
161 | } | ||
162 | |||
163 | using (Stream outputStream = File.Create(output)) | ||
164 | { | ||
165 | MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass); | ||
166 | } | ||
167 | |||
168 | MakeSfxCA.CopyVersionResource(customActionAssembly, output); | ||
169 | |||
170 | MakeSfxCA.PackInputFiles(output, inputsMap); | ||
171 | |||
172 | log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName); | ||
173 | } | ||
174 | |||
175 | /// <summary> | ||
176 | /// Splits any list items delimited by semicolons into separate items. | ||
177 | /// </summary> | ||
178 | /// <param name="list">Read-only input list.</param> | ||
179 | /// <returns>New list with resulting split items.</returns> | ||
180 | private static IList<string> SplitList(IList<string> list) | ||
181 | { | ||
182 | var newList = new List<string>(list.Count); | ||
183 | |||
184 | foreach (var item in list) | ||
185 | { | ||
186 | if (!string.IsNullOrEmpty(item)) | ||
187 | { | ||
188 | foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) | ||
189 | { | ||
190 | newList.Add(splitItem); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | return newList; | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection. | ||
200 | /// </summary> | ||
201 | /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param> | ||
202 | /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param> | ||
203 | /// <remarks> | ||
204 | /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them | ||
205 | /// to the list of input files if found. | ||
206 | /// </remarks> | ||
207 | private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir) | ||
208 | { | ||
209 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args) | ||
210 | { | ||
211 | AssemblyName resolveName = new AssemblyName(args.Name); | ||
212 | Assembly assembly = null; | ||
213 | |||
214 | // First, try to find the assembly in the list of input files. | ||
215 | foreach (var inputFile in inputFiles.Values) | ||
216 | { | ||
217 | var inputName = Path.GetFileNameWithoutExtension(inputFile); | ||
218 | var inputExtension = Path.GetExtension(inputFile); | ||
219 | if (string.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) && | ||
220 | (string.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) || | ||
221 | string.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase))) | ||
222 | { | ||
223 | assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile); | ||
224 | |||
225 | if (assembly != null) | ||
226 | { | ||
227 | break; | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | |||
232 | // Second, try to find the assembly in the input directory. | ||
233 | if (assembly == null && inputDir != null) | ||
234 | { | ||
235 | string assemblyPath = null; | ||
236 | if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll")) | ||
237 | { | ||
238 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll"; | ||
239 | } | ||
240 | else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe")) | ||
241 | { | ||
242 | assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe"; | ||
243 | } | ||
244 | |||
245 | if (assemblyPath != null) | ||
246 | { | ||
247 | assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath); | ||
248 | |||
249 | if (assembly != null) | ||
250 | { | ||
251 | // Add this detected dependency to the list of files to be packed. | ||
252 | inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath); | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | // Third, try to load the assembly from the GAC. | ||
258 | if (assembly == null) | ||
259 | { | ||
260 | try | ||
261 | { | ||
262 | assembly = Assembly.ReflectionOnlyLoad(args.Name); | ||
263 | } | ||
264 | catch (FileNotFoundException) | ||
265 | { | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (assembly != null) | ||
270 | { | ||
271 | if (string.Equals(assembly.GetName().ToString(), resolveName.ToString())) | ||
272 | { | ||
273 | log.WriteLine(" Loaded dependent assembly: " + assembly.Location); | ||
274 | return assembly; | ||
275 | } | ||
276 | |||
277 | log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location); | ||
278 | log.WriteLine(" Loaded assembly : " + assembly.GetName()); | ||
279 | log.WriteLine(" Reference assembly: " + resolveName); | ||
280 | } | ||
281 | else | ||
282 | { | ||
283 | log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName); | ||
284 | } | ||
285 | |||
286 | return null; | ||
287 | }; | ||
288 | } | ||
289 | |||
290 | /// <summary> | ||
291 | /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails. | ||
292 | /// </summary> | ||
293 | /// <param name="assemblyPath">Path of the assembly file to laod.</param> | ||
294 | /// <returns>Loaded assembly, or null if the load failed.</returns> | ||
295 | private static Assembly TryLoadDependentAssembly(string assemblyPath) | ||
296 | { | ||
297 | Assembly assembly = null; | ||
298 | try | ||
299 | { | ||
300 | assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); | ||
301 | } | ||
302 | catch (IOException ex) | ||
303 | { | ||
304 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
305 | } | ||
306 | catch (BadImageFormatException ex) | ||
307 | { | ||
308 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
309 | } | ||
310 | catch (SecurityException ex) | ||
311 | { | ||
312 | log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message); | ||
313 | } | ||
314 | |||
315 | return assembly; | ||
316 | } | ||
317 | |||
318 | /// <summary> | ||
319 | /// Searches the types in the input assembly for a type that implements IEmbeddedUI. | ||
320 | /// </summary> | ||
321 | /// <param name="module"></param> | ||
322 | /// <returns></returns> | ||
323 | private static string FindEmbeddedUIClass(string module) | ||
324 | { | ||
325 | log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module)); | ||
326 | |||
327 | string uiClass = null; | ||
328 | |||
329 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
330 | |||
331 | foreach (var type in assembly.GetExportedTypes()) | ||
332 | { | ||
333 | if (!type.IsAbstract) | ||
334 | { | ||
335 | foreach (var interfaceType in type.GetInterfaces()) | ||
336 | { | ||
337 | if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI") | ||
338 | { | ||
339 | if (uiClass == null) | ||
340 | { | ||
341 | uiClass = assembly.GetName().Name + "!" + type.FullName; | ||
342 | } | ||
343 | else | ||
344 | { | ||
345 | throw new ArgumentException("Multiple IEmbeddedUI implementations found."); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | |||
352 | return uiClass; | ||
353 | } | ||
354 | |||
355 | /// <summary> | ||
356 | /// Reflects on an input CA module to locate custom action entry-points. | ||
357 | /// </summary> | ||
358 | /// <param name="module">Assembly module with CA entry-points.</param> | ||
359 | /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns> | ||
360 | private static IDictionary<string, string> FindEntryPoints(string module) | ||
361 | { | ||
362 | log.WriteLine("Searching for custom action entry points " + | ||
363 | "in {0}", Path.GetFileName(module)); | ||
364 | |||
365 | var entryPoints = new Dictionary<string, string>(); | ||
366 | |||
367 | var assembly = Assembly.ReflectionOnlyLoadFrom(module); | ||
368 | |||
369 | foreach (var type in assembly.GetExportedTypes()) | ||
370 | { | ||
371 | foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) | ||
372 | { | ||
373 | var entryPointName = MakeSfxCA.GetEntryPoint(method); | ||
374 | if (entryPointName != null) | ||
375 | { | ||
376 | var entryPointPath = string.Format( | ||
377 | "{0}!{1}.{2}", | ||
378 | Path.GetFileNameWithoutExtension(module), | ||
379 | type.FullName, | ||
380 | method.Name); | ||
381 | entryPoints.Add(entryPointName, entryPointPath); | ||
382 | |||
383 | log.WriteLine(" {0}={1}", entryPointName, entryPointPath); | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | |||
388 | return entryPoints; | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method. | ||
393 | /// </summary> | ||
394 | /// <param name="method">A public static method.</param> | ||
395 | /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name, | ||
396 | /// or null if the method is not a custom action method.</returns> | ||
397 | private static string GetEntryPoint(MethodInfo method) | ||
398 | { | ||
399 | IList<CustomAttributeData> attributes; | ||
400 | try | ||
401 | { | ||
402 | attributes = CustomAttributeData.GetCustomAttributes(method); | ||
403 | } | ||
404 | catch (FileLoadException) | ||
405 | { | ||
406 | // Already logged load failures in the assembly-resolve-handler. | ||
407 | return null; | ||
408 | } | ||
409 | |||
410 | foreach (CustomAttributeData attribute in attributes) | ||
411 | { | ||
412 | if (attribute.ToString().StartsWith( | ||
413 | "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(", | ||
414 | StringComparison.Ordinal)) | ||
415 | { | ||
416 | string entryPointName = null; | ||
417 | foreach (var argument in attribute.ConstructorArguments) | ||
418 | { | ||
419 | // The entry point name is the first positional argument, if specified. | ||
420 | entryPointName = (string) argument.Value; | ||
421 | break; | ||
422 | } | ||
423 | |||
424 | if (string.IsNullOrEmpty(entryPointName)) | ||
425 | { | ||
426 | entryPointName = method.Name; | ||
427 | } | ||
428 | |||
429 | return entryPointName; | ||
430 | } | ||
431 | } | ||
432 | |||
433 | return null; | ||
434 | } | ||
435 | |||
436 | /// <summary> | ||
437 | /// Counts the number of template entrypoints in SfxCA.dll. | ||
438 | /// </summary> | ||
439 | /// <remarks> | ||
440 | /// Depending on the requirements, SfxCA.dll might be built with | ||
441 | /// more entrypoints than the default. | ||
442 | /// </remarks> | ||
443 | private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat) | ||
444 | { | ||
445 | for (var count = 0; ; count++) | ||
446 | { | ||
447 | var templateName = string.Format(entryPointFormat, count); | ||
448 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
449 | |||
450 | var nameOffset = FindBytes(fileBytes, templateAsciiBytes); | ||
451 | if (nameOffset < 0) | ||
452 | { | ||
453 | return count; | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | |||
458 | /// <summary> | ||
459 | /// Writes a modified version of SfxCA.dll to the output stream, | ||
460 | /// with the template entry-points mapped to the CA entry-points. | ||
461 | /// </summary> | ||
462 | /// <remarks> | ||
463 | /// To avoid having to recompile SfxCA.dll for every different set of CAs, | ||
464 | /// this method looks for a preset number of template entry-points in the | ||
465 | /// binary file and overwrites their entrypoint name and string data with | ||
466 | /// CA-specific values. | ||
467 | /// </remarks> | ||
468 | private static void WriteEntryModule( | ||
469 | string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass) | ||
470 | { | ||
471 | log.WriteLine("Modifying SfxCA.dll stub"); | ||
472 | |||
473 | byte[] fileBytes; | ||
474 | using (var readStream = File.OpenRead(sfxDll)) | ||
475 | { | ||
476 | fileBytes = new byte[(int) readStream.Length]; | ||
477 | readStream.Read(fileBytes, 0, fileBytes.Length); | ||
478 | } | ||
479 | |||
480 | const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}"; | ||
481 | const int MAX_ENTRYPOINT_NAME = 72; | ||
482 | const int MAX_ENTRYPOINT_PATH = 160; | ||
483 | //var emptyBytes = new byte[0]; | ||
484 | |||
485 | var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT); | ||
486 | |||
487 | if (slotCount == 0) | ||
488 | { | ||
489 | throw new ArgumentException("Invalid SfxCA.dll file."); | ||
490 | } | ||
491 | |||
492 | if (entryPoints.Count > slotCount) | ||
493 | { | ||
494 | throw new ArgumentException(string.Format( | ||
495 | "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " + | ||
496 | "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.", | ||
497 | entryPoints.Count, slotCount)); | ||
498 | } | ||
499 | |||
500 | var slotSort = new string[slotCount]; | ||
501 | for (var i = 0; i < slotCount - entryPoints.Count; i++) | ||
502 | { | ||
503 | slotSort[i] = string.Empty; | ||
504 | } | ||
505 | |||
506 | entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count); | ||
507 | Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal); | ||
508 | |||
509 | for (var i = 0; ; i++) | ||
510 | { | ||
511 | var templateName = string.Format(ENTRYPOINT_FORMAT, i); | ||
512 | var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName); | ||
513 | var templateUniBytes = Encoding.Unicode.GetBytes(templateName); | ||
514 | |||
515 | var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes); | ||
516 | if (nameOffset < 0) | ||
517 | { | ||
518 | break; | ||
519 | } | ||
520 | |||
521 | var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes); | ||
522 | if (pathOffset < 0) | ||
523 | { | ||
524 | break; | ||
525 | } | ||
526 | |||
527 | var entryPointName = slotSort[i]; | ||
528 | var entryPointPath = entryPointName.Length > 0 ? | ||
529 | entryPoints[entryPointName] : string.Empty; | ||
530 | |||
531 | if (entryPointName.Length > MAX_ENTRYPOINT_NAME) | ||
532 | { | ||
533 | throw new ArgumentException(string.Format( | ||
534 | "Entry point name exceeds limit of {0} characters: {1}", | ||
535 | MAX_ENTRYPOINT_NAME, | ||
536 | entryPointName)); | ||
537 | } | ||
538 | |||
539 | if (entryPointPath.Length > MAX_ENTRYPOINT_PATH) | ||
540 | { | ||
541 | throw new ArgumentException(string.Format( | ||
542 | "Entry point path exceeds limit of {0} characters: {1}", | ||
543 | MAX_ENTRYPOINT_PATH, | ||
544 | entryPointPath)); | ||
545 | } | ||
546 | |||
547 | var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName); | ||
548 | var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath); | ||
549 | |||
550 | MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes); | ||
551 | MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes); | ||
552 | } | ||
553 | |||
554 | if (entryPoints.Count == 0 && uiClass != null) | ||
555 | { | ||
556 | // Remove the zzz prefix from exported EmbeddedUI entry-points. | ||
557 | foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" }) | ||
558 | { | ||
559 | var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export); | ||
560 | |||
561 | var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes); | ||
562 | if (exportOffset < 0) | ||
563 | { | ||
564 | throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export); | ||
565 | } | ||
566 | |||
567 | var replaceNameBytes = Encoding.ASCII.GetBytes(export); | ||
568 | MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes); | ||
569 | } | ||
570 | |||
571 | if (uiClass.Length > MAX_ENTRYPOINT_PATH) | ||
572 | { | ||
573 | throw new ArgumentException(string.Format( | ||
574 | "UI class full name exceeds limit of {0} characters: {1}", | ||
575 | MAX_ENTRYPOINT_PATH, | ||
576 | uiClass)); | ||
577 | } | ||
578 | |||
579 | var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName"); | ||
580 | var replaceBytes = Encoding.Unicode.GetBytes(uiClass); | ||
581 | |||
582 | // Fill in the embedded UI implementor class so the proxy knows which one to load. | ||
583 | var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes); | ||
584 | if (replaceOffset >= 0) | ||
585 | { | ||
586 | MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes); | ||
587 | } | ||
588 | } | ||
589 | |||
590 | outputStream.Write(fileBytes, 0, fileBytes.Length); | ||
591 | } | ||
592 | |||
593 | /// <summary> | ||
594 | /// Searches for a sub-array of bytes within a larger array of bytes. | ||
595 | /// </summary> | ||
596 | private static int FindBytes(byte[] source, byte[] find) | ||
597 | { | ||
598 | for (var i = 0; i < source.Length; i++) | ||
599 | { | ||
600 | int j; | ||
601 | for (j = 0; j < find.Length; j++) | ||
602 | { | ||
603 | if (source[i + j] != find[j]) | ||
604 | { | ||
605 | break; | ||
606 | } | ||
607 | } | ||
608 | |||
609 | if (j == find.Length) | ||
610 | { | ||
611 | return i; | ||
612 | } | ||
613 | } | ||
614 | |||
615 | return -1; | ||
616 | } | ||
617 | |||
618 | /// <summary> | ||
619 | /// Replaces a range of bytes with new bytes, padding any extra part | ||
620 | /// of the range with zeroes. | ||
621 | /// </summary> | ||
622 | private static void ReplaceBytes( | ||
623 | byte[] source, int offset, int length, byte[] replace) | ||
624 | { | ||
625 | for (var i = 0; i < length; i++) | ||
626 | { | ||
627 | if (i < replace.Length) | ||
628 | { | ||
629 | source[offset + i] = replace[i]; | ||
630 | } | ||
631 | else | ||
632 | { | ||
633 | source[offset + i] = 0; | ||
634 | } | ||
635 | } | ||
636 | } | ||
637 | |||
638 | /// <summary> | ||
639 | /// Print the name of one file as it is being packed into the cab. | ||
640 | /// </summary> | ||
641 | private static void PackProgress(object source, ArchiveProgressEventArgs e) | ||
642 | { | ||
643 | if (e.ProgressType == ArchiveProgressType.StartFile && log != null) | ||
644 | { | ||
645 | log.WriteLine(" {0}", e.CurrentFileName); | ||
646 | } | ||
647 | } | ||
648 | |||
649 | /// <summary> | ||
650 | /// Gets a mapping from filenames as they will be in the cab to filenames | ||
651 | /// as they are currently on disk. | ||
652 | /// </summary> | ||
653 | /// <remarks> | ||
654 | /// By default, all files will be placed in the root of the cab. But inputs may | ||
655 | /// optionally include an alternate inside-cab file path before an equals sign. | ||
656 | /// </remarks> | ||
657 | private static IDictionary<string, string> GetPackFileMap(IList<string> inputs) | ||
658 | { | ||
659 | var fileMap = new Dictionary<string, string>(); | ||
660 | foreach (var inputFile in inputs) | ||
661 | { | ||
662 | if (inputFile.IndexOf('=') > 0) | ||
663 | { | ||
664 | var parse = inputFile.Split('='); | ||
665 | if (!fileMap.ContainsKey(parse[0])) | ||
666 | { | ||
667 | fileMap.Add(parse[0], parse[1]); | ||
668 | } | ||
669 | } | ||
670 | else | ||
671 | { | ||
672 | var fileName = Path.GetFileName(inputFile); | ||
673 | if (!fileMap.ContainsKey(fileName)) | ||
674 | { | ||
675 | fileMap.Add(fileName, inputFile); | ||
676 | } | ||
677 | } | ||
678 | } | ||
679 | return fileMap; | ||
680 | } | ||
681 | |||
682 | /// <summary> | ||
683 | /// Packs the input files into a cab that is appended to the | ||
684 | /// output SfxCA.dll. | ||
685 | /// </summary> | ||
686 | private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap) | ||
687 | { | ||
688 | log.WriteLine("Packaging files"); | ||
689 | |||
690 | var cabInfo = new CabInfo(outputFile); | ||
691 | cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress); | ||
692 | } | ||
693 | |||
694 | /// <summary> | ||
695 | /// Copies the version resource information from the CA module to | ||
696 | /// the CA package. This gives the package the file version and | ||
697 | /// description of the CA module, instead of the version and | ||
698 | /// description of SfxCA.dll. | ||
699 | /// </summary> | ||
700 | private static void CopyVersionResource(string sourceFile, string destFile) | ||
701 | { | ||
702 | log.WriteLine("Copying file version info from {0} to {1}", | ||
703 | sourceFile, destFile); | ||
704 | |||
705 | var rc = new ResourceCollection(); | ||
706 | rc.Find(sourceFile, ResourceType.Version); | ||
707 | rc.Load(sourceFile); | ||
708 | rc.Save(destFile); | ||
709 | } | ||
710 | } | ||
711 | } | ||
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 | |||
5 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); | ||
6 | |||
7 | //--------------------------------------------------------------------- | ||
8 | // CLR HOSTING | ||
9 | //--------------------------------------------------------------------- | ||
10 | |||
11 | /// <summary> | ||
12 | /// Binds to the CLR after determining the appropriate version. | ||
13 | /// </summary> | ||
14 | /// <param name="hSession">Handle to the installer session, | ||
15 | /// used just for logging.</param> | ||
16 | /// <param name="version">Specific version of the CLR to load. | ||
17 | /// If null, then the config file and/or primary assembly are | ||
18 | /// used to determine the version.</param> | ||
19 | /// <param name="szConfigFile">XML .config file which may contain | ||
20 | /// a startup section to direct which version of the CLR to use. | ||
21 | /// May be NULL.</param> | ||
22 | /// <param name="szPrimaryAssembly">Assembly to be used to determine | ||
23 | /// the version of the CLR in the absence of other configuration. | ||
24 | /// May be NULL.</param> | ||
25 | /// <param name="ppHost">Returned runtime host interface.</param> | ||
26 | /// <returns>True if the CLR was loaded successfully, false if | ||
27 | /// there was some error.</returns> | ||
28 | /// <remarks> | ||
29 | /// If szPrimaryAssembly is NULL and szConfigFile is also NULL or | ||
30 | /// does not contain any version configuration, the CLR will not be loaded. | ||
31 | /// </remarks> | ||
32 | bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, | ||
33 | const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost) | ||
34 | { | ||
35 | typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion, | ||
36 | LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags, | ||
37 | LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, | ||
38 | LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength); | ||
39 | typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, | ||
40 | DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv); | ||
41 | |||
42 | HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll"); | ||
43 | if (hmodMscoree == NULL) | ||
44 | { | ||
45 | Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action " | ||
46 | L"requires the .NET Framework to be installed.", GetLastError()); | ||
47 | return false; | ||
48 | } | ||
49 | PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo) | ||
50 | GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo"); | ||
51 | PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx) | ||
52 | GetProcAddress(hmodMscoree, "CorBindToRuntimeEx"); | ||
53 | if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL) | ||
54 | { | ||
55 | Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action " | ||
56 | L"requires the .NET Framework to be installed.", GetLastError()); | ||
57 | FreeLibrary(hmodMscoree); | ||
58 | return false; | ||
59 | } | ||
60 | |||
61 | wchar_t szClrVersion[20]; | ||
62 | HRESULT hr; | ||
63 | |||
64 | if (szVersion != NULL && szVersion[0] != L'\0') | ||
65 | { | ||
66 | wcsncpy_s(szClrVersion, 20, szVersion, 20); | ||
67 | } | ||
68 | else | ||
69 | { | ||
70 | wchar_t szVersionDir[MAX_PATH]; | ||
71 | hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL, | ||
72 | szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL); | ||
73 | if (FAILED(hr)) | ||
74 | { | ||
75 | Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr); | ||
76 | Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or " | ||
77 | L"that there is a matching supportedRuntime element in CustomAction.config. " | ||
78 | L"If you are binding to .NET 4 or greater add " | ||
79 | L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element."); | ||
80 | FreeLibrary(hmodMscoree); | ||
81 | return false; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | Log(hSession, L"Binding to CLR version %s", szClrVersion); | ||
86 | |||
87 | ICorRuntimeHost* pHost; | ||
88 | hr = pCorBindToRuntimeEx(szClrVersion, NULL, | ||
89 | STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, | ||
90 | CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost); | ||
91 | if (FAILED(hr)) | ||
92 | { | ||
93 | Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr); | ||
94 | FreeLibrary(hmodMscoree); | ||
95 | return false; | ||
96 | } | ||
97 | hr = pHost->Start(); | ||
98 | if (FAILED(hr)) | ||
99 | { | ||
100 | Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr); | ||
101 | pHost->Release(); | ||
102 | FreeLibrary(hmodMscoree); | ||
103 | return false; | ||
104 | } | ||
105 | *ppHost = pHost; | ||
106 | FreeLibrary(hmodMscoree); | ||
107 | return true; | ||
108 | } | ||
109 | |||
110 | /// <summary> | ||
111 | /// Creates a new CLR application domain. | ||
112 | /// </summary> | ||
113 | /// <param name="hSession">Handle to the installer session, | ||
114 | /// used just for logging</param> | ||
115 | /// <param name="pHost">Interface to the runtime host where the | ||
116 | /// app domain will be created.</param> | ||
117 | /// <param name="szName">Name of the app domain to create.</param> | ||
118 | /// <param name="szAppBase">Application base directory path, where | ||
119 | /// the app domain will look first to load its assemblies.</param> | ||
120 | /// <param name="szConfigFile">Optional XML .config file containing any | ||
121 | /// configuration for thae app domain.</param> | ||
122 | /// <param name="ppAppDomain">Returned app domain interface.</param> | ||
123 | /// <returns>True if the app domain was created successfully, false if | ||
124 | /// there was some error.</returns> | ||
125 | bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, | ||
126 | const wchar_t* szName, const wchar_t* szAppBase, | ||
127 | const wchar_t* szConfigFile, _AppDomain** ppAppDomain) | ||
128 | { | ||
129 | IUnknown* punkAppDomainSetup = NULL; | ||
130 | IAppDomainSetup* pAppDomainSetup = NULL; | ||
131 | HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup); | ||
132 | if (SUCCEEDED(hr)) | ||
133 | { | ||
134 | hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup); | ||
135 | punkAppDomainSetup->Release(); | ||
136 | } | ||
137 | if (FAILED(hr)) | ||
138 | { | ||
139 | Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr); | ||
140 | return false; | ||
141 | } | ||
142 | |||
143 | const wchar_t* szUrlPrefix = L"file:///"; | ||
144 | size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase); | ||
145 | wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t)); | ||
146 | if (szApplicationBase == NULL) hr = E_OUTOFMEMORY; | ||
147 | else | ||
148 | { | ||
149 | StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix); | ||
150 | StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase); | ||
151 | BSTR bstrApplicationBase = SysAllocString(szApplicationBase); | ||
152 | if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY; | ||
153 | else | ||
154 | { | ||
155 | hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase); | ||
156 | SysFreeString(bstrApplicationBase); | ||
157 | } | ||
158 | } | ||
159 | |||
160 | if (SUCCEEDED(hr) && szConfigFile != NULL) | ||
161 | { | ||
162 | BSTR bstrConfigFile = SysAllocString(szConfigFile); | ||
163 | if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY; | ||
164 | else | ||
165 | { | ||
166 | hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile); | ||
167 | SysFreeString(bstrConfigFile); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | if (FAILED(hr)) | ||
172 | { | ||
173 | Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr); | ||
174 | pAppDomainSetup->Release(); | ||
175 | return false; | ||
176 | } | ||
177 | |||
178 | IUnknown* punkAppDomain; | ||
179 | hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain); | ||
180 | pAppDomainSetup->Release(); | ||
181 | if (SUCCEEDED(hr)) | ||
182 | { | ||
183 | hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain); | ||
184 | punkAppDomain->Release(); | ||
185 | } | ||
186 | |||
187 | if (FAILED(hr)) | ||
188 | { | ||
189 | Log(hSession, L"Failed to create app domain. Error code 0x%X", hr); | ||
190 | return false; | ||
191 | } | ||
192 | |||
193 | return true; | ||
194 | } | ||
195 | |||
196 | /// <summary> | ||
197 | /// Locates a specific method in a specific class and assembly. | ||
198 | /// </summary> | ||
199 | /// <param name="hSession">Handle to the installer session, | ||
200 | /// used just for logging</param> | ||
201 | /// <param name="pAppDomain">Application domain in which to | ||
202 | /// load assemblies.</param> | ||
203 | /// <param name="szAssembly">Display name of the assembly | ||
204 | /// containing the method.</param> | ||
205 | /// <param name="szClass">Fully-qualified name of the class | ||
206 | /// containing the method.</param> | ||
207 | /// <param name="szMethod">Name of the method.</param> | ||
208 | /// <param name="ppMethod">Returned method interface.</param> | ||
209 | /// <returns>True if the method was located, otherwise false.</returns> | ||
210 | /// <remarks>Only public static methods are searched. Method | ||
211 | /// parameter types are not considered; if there are multiple | ||
212 | /// matching methods with different parameters, an error results.</remarks> | ||
213 | bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
214 | const wchar_t* szAssembly, const wchar_t* szClass, | ||
215 | const wchar_t* szMethod, _MethodInfo** ppMethod) | ||
216 | { | ||
217 | HRESULT hr; | ||
218 | _Assembly* pAssembly = NULL; | ||
219 | BSTR bstrAssemblyName = SysAllocString(szAssembly); | ||
220 | if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY; | ||
221 | else | ||
222 | { | ||
223 | hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly); | ||
224 | SysFreeString(bstrAssemblyName); | ||
225 | } | ||
226 | if (FAILED(hr)) | ||
227 | { | ||
228 | Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr); | ||
229 | return false; | ||
230 | } | ||
231 | |||
232 | _Type* pType = NULL; | ||
233 | BSTR bstrClass = SysAllocString(szClass); | ||
234 | if (bstrClass == NULL) hr = E_OUTOFMEMORY; | ||
235 | else | ||
236 | { | ||
237 | hr = pAssembly->GetType_2(bstrClass, &pType); | ||
238 | SysFreeString(bstrClass); | ||
239 | } | ||
240 | pAssembly->Release(); | ||
241 | if (FAILED(hr) || pType == NULL) | ||
242 | { | ||
243 | Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr); | ||
244 | return false; | ||
245 | } | ||
246 | |||
247 | BSTR bstrMethod = SysAllocString(szMethod); | ||
248 | if (bstrMethod == NULL) hr = E_OUTOFMEMORY; | ||
249 | else | ||
250 | { | ||
251 | hr = pType->GetMethod_2(bstrMethod, | ||
252 | (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod); | ||
253 | SysFreeString(bstrMethod); | ||
254 | } | ||
255 | pType->Release(); | ||
256 | if (FAILED(hr) || *ppMethod == NULL) | ||
257 | { | ||
258 | Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr); | ||
259 | return false; | ||
260 | } | ||
261 | return true; | ||
262 | } | ||
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. | ||
7 | static const wchar_t* g_szWorkingDir; | ||
8 | static ICorRuntimeHost* g_pClrHost; | ||
9 | static _AppDomain* g_pAppDomain; | ||
10 | static _MethodInfo* g_pProcessMessageMethod; | ||
11 | static _MethodInfo* g_pShutdownMethod; | ||
12 | |||
13 | // Reserve extra space for strings to be replaced at build time. | ||
14 | #define NULLSPACE \ | ||
15 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
16 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
17 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
18 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
19 | |||
20 | // Prototypes for local functions. | ||
21 | // See the function definitions for comments. | ||
22 | |||
23 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, | ||
24 | const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult); | ||
25 | |||
26 | /// <summary> | ||
27 | /// First entry-point for the UI DLL when loaded and called by MSI. | ||
28 | /// Extracts the payload, hosts the CLR, and invokes the managed | ||
29 | /// initialize method. | ||
30 | /// </summary> | ||
31 | /// <param name="hSession">Handle to the installer session, | ||
32 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
33 | /// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table | ||
34 | /// have been extracted, and where additional payload from this package will be extracted.</param> | ||
35 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
36 | /// the managed initialize method.</param> | ||
37 | extern "C" | ||
38 | UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel) | ||
39 | { | ||
40 | // If the managed initialize method cannot be called, continue the installation in BASIC UI mode. | ||
41 | UINT uiResult = INSTALLUILEVEL_BASIC; | ||
42 | |||
43 | const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE; | ||
44 | |||
45 | g_szWorkingDir = szResourcePath; | ||
46 | |||
47 | wchar_t szModule[MAX_PATH]; | ||
48 | DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1); | ||
49 | if (cchCopied == 0) | ||
50 | { | ||
51 | Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); | ||
52 | return uiResult; | ||
53 | } | ||
54 | else if (cchCopied == MAX_PATH - 1) | ||
55 | { | ||
56 | Log(hSession, L"Failed to get module path -- path is too long."); | ||
57 | return uiResult; | ||
58 | } | ||
59 | |||
60 | Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir); | ||
61 | int err = ExtractCabinet(szModule, g_szWorkingDir); | ||
62 | if (err != 0) | ||
63 | { | ||
64 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); | ||
65 | Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as " | ||
66 | L"any file contained in the embedded UI package."); | ||
67 | return uiResult; | ||
68 | } | ||
69 | |||
70 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
71 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir); | ||
72 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config"); | ||
73 | |||
74 | const wchar_t* szConfigFile = szConfigFilePath; | ||
75 | if (!PathFileExists(szConfigFilePath)) | ||
76 | { | ||
77 | szConfigFile = NULL; | ||
78 | } | ||
79 | |||
80 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
81 | StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir); | ||
82 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
83 | |||
84 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost)) | ||
85 | { | ||
86 | if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir, | ||
87 | szConfigFile, &g_pAppDomain)) | ||
88 | { | ||
89 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
90 | const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy"; | ||
91 | const wchar_t* szInitMethod = L"Initialize"; | ||
92 | const wchar_t* szProcessMessageMethod = L"ProcessMessage"; | ||
93 | const wchar_t* szShutdownMethod = L"Shutdown"; | ||
94 | |||
95 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
96 | szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) && | ||
97 | GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
98 | szProxyClass, szShutdownMethod, &g_pShutdownMethod)) | ||
99 | { | ||
100 | _MethodInfo* pInitMethod; | ||
101 | if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName, | ||
102 | szProxyClass, szInitMethod, &pInitMethod)) | ||
103 | { | ||
104 | bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult); | ||
105 | pInitMethod->Release(); | ||
106 | if (invokeSuccess) | ||
107 | { | ||
108 | if (uiResult == 0) | ||
109 | { | ||
110 | return ERROR_SUCCESS; | ||
111 | } | ||
112 | else if (uiResult == ERROR_INSTALL_USEREXIT) | ||
113 | { | ||
114 | // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT. | ||
115 | // So return success here and then IDCANCEL on the next progress message. | ||
116 | uiResult = 0; | ||
117 | *pdwInternalUILevel = INSTALLUILEVEL_NONE; | ||
118 | Log(hSession, L"Initialization canceled by user."); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | g_pProcessMessageMethod->Release(); | ||
125 | g_pProcessMessageMethod = NULL; | ||
126 | g_pShutdownMethod->Release(); | ||
127 | g_pShutdownMethod = NULL; | ||
128 | |||
129 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
130 | g_pAppDomain->Release(); | ||
131 | g_pAppDomain = NULL; | ||
132 | } | ||
133 | g_pClrHost->Stop(); | ||
134 | g_pClrHost->Release(); | ||
135 | g_pClrHost = NULL; | ||
136 | } | ||
137 | |||
138 | return uiResult; | ||
139 | } | ||
140 | |||
141 | /// <summary> | ||
142 | /// Entry-point for UI progress messages received from the MSI engine during an active installation. | ||
143 | /// Forwards the progress messages to the managed handler method and returns its result. | ||
144 | /// </summary> | ||
145 | extern "C" | ||
146 | INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord) | ||
147 | { | ||
148 | if (g_pProcessMessageMethod == NULL) | ||
149 | { | ||
150 | // Initialization was canceled. | ||
151 | return IDCANCEL; | ||
152 | } | ||
153 | |||
154 | VARIANT vResult; | ||
155 | VariantInit(&vResult); | ||
156 | |||
157 | VARIANT vNull; | ||
158 | vNull.vt = VT_EMPTY; | ||
159 | |||
160 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2); | ||
161 | VARIANT vMessageType; | ||
162 | vMessageType.vt = VT_I4; | ||
163 | vMessageType.lVal = (LONG) uiMessageType; | ||
164 | LONG index = 0; | ||
165 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType); | ||
166 | if (FAILED(hr)) goto LExit; | ||
167 | VARIANT vRecord; | ||
168 | vRecord.vt = VT_I4; | ||
169 | vRecord.lVal = (LONG) hRecord; | ||
170 | index = 1; | ||
171 | hr = SafeArrayPutElement(saArgs, &index, &vRecord); | ||
172 | if (FAILED(hr)) goto LExit; | ||
173 | |||
174 | hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult); | ||
175 | |||
176 | LExit: | ||
177 | SafeArrayDestroy(saArgs); | ||
178 | if (SUCCEEDED(hr)) | ||
179 | { | ||
180 | return vResult.intVal; | ||
181 | } | ||
182 | else | ||
183 | { | ||
184 | return -1; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Entry-point for the UI shutdown message received from the MSI engine after installation has completed. | ||
190 | /// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR. | ||
191 | /// </summary> | ||
192 | extern "C" | ||
193 | DWORD __stdcall ShutdownEmbeddedUI() | ||
194 | { | ||
195 | if (g_pShutdownMethod != NULL) | ||
196 | { | ||
197 | VARIANT vNull; | ||
198 | vNull.vt = VT_EMPTY; | ||
199 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0); | ||
200 | g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL); | ||
201 | SafeArrayDestroy(saArgs); | ||
202 | |||
203 | g_pClrHost->UnloadDomain(g_pAppDomain); | ||
204 | g_pAppDomain->Release(); | ||
205 | g_pClrHost->Stop(); | ||
206 | g_pClrHost->Release(); | ||
207 | } | ||
208 | |||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | /// <summary> | ||
213 | /// Loads and invokes the managed portion of the proxy. | ||
214 | /// </summary> | ||
215 | /// <param name="pInitMethod">Managed initialize method to be invoked.</param> | ||
216 | /// <param name="hSession">Handle to the installer session, | ||
217 | /// used for logging errors and to be passed on to the managed initialize method.</param> | ||
218 | /// <param name="szClassName">Name of the UI class to be loaded. | ||
219 | /// This must be of the form: AssemblyName!Namespace.Class</param> | ||
220 | /// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from | ||
221 | /// the managed initialize method.</param> | ||
222 | /// <param name="puiResult">Return value of the invoked initialize method.</param> | ||
223 | /// <returns>True if the managed proxy was invoked successfully, or an | ||
224 | /// error code if there was some error. Note the initialize method itself may | ||
225 | /// return an error via puiResult while this method still returns true | ||
226 | /// since the invocation was successful.</returns> | ||
227 | bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult) | ||
228 | { | ||
229 | VARIANT vResult; | ||
230 | VariantInit(&vResult); | ||
231 | |||
232 | VARIANT vNull; | ||
233 | vNull.vt = VT_EMPTY; | ||
234 | |||
235 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
236 | VARIANT vSessionHandle; | ||
237 | vSessionHandle.vt = VT_I4; | ||
238 | vSessionHandle.lVal = (LONG) hSession; | ||
239 | LONG index = 0; | ||
240 | HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
241 | if (FAILED(hr)) goto LExit; | ||
242 | VARIANT vEntryPoint; | ||
243 | vEntryPoint.vt = VT_BSTR; | ||
244 | vEntryPoint.bstrVal = SysAllocString(szClassName); | ||
245 | if (vEntryPoint.bstrVal == NULL) | ||
246 | { | ||
247 | hr = E_OUTOFMEMORY; | ||
248 | goto LExit; | ||
249 | } | ||
250 | index = 1; | ||
251 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
252 | if (FAILED(hr)) goto LExit; | ||
253 | VARIANT vUILevel; | ||
254 | vUILevel.vt = VT_I4; | ||
255 | vUILevel.ulVal = *pdwInternalUILevel; | ||
256 | index = 2; | ||
257 | hr = SafeArrayPutElement(saArgs, &index, &vUILevel); | ||
258 | if (FAILED(hr)) goto LExit; | ||
259 | |||
260 | hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult); | ||
261 | |||
262 | LExit: | ||
263 | SafeArrayDestroy(saArgs); | ||
264 | if (SUCCEEDED(hr)) | ||
265 | { | ||
266 | *puiResult = (UINT) vResult.lVal; | ||
267 | if ((*puiResult & 0xFFFF) == 0) | ||
268 | { | ||
269 | // Due to interop limitations, the successful resulting UILevel is returned | ||
270 | // as the high-word of the return value instead of via a ref parameter. | ||
271 | *pdwInternalUILevel = *puiResult >> 16; | ||
272 | *puiResult = 0; | ||
273 | } | ||
274 | return true; | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr); | ||
279 | return false; | ||
280 | } | ||
281 | } | ||
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 | |||
4 | LIBRARY "SfxCA" | ||
5 | |||
6 | EXPORTS | ||
7 | |||
8 | CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000 | ||
9 | CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001 | ||
10 | CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002 | ||
11 | CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003 | ||
12 | CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004 | ||
13 | CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005 | ||
14 | CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006 | ||
15 | CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007 | ||
16 | CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008 | ||
17 | CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009 | ||
18 | CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010 | ||
19 | CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011 | ||
20 | CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012 | ||
21 | CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013 | ||
22 | CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014 | ||
23 | CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015 | ||
24 | CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016 | ||
25 | CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017 | ||
26 | CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018 | ||
27 | CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019 | ||
28 | CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020 | ||
29 | CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021 | ||
30 | CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022 | ||
31 | CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023 | ||
32 | CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024 | ||
33 | CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025 | ||
34 | CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026 | ||
35 | CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027 | ||
36 | CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028 | ||
37 | CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029 | ||
38 | CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030 | ||
39 | CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031 | ||
40 | CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032 | ||
41 | CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033 | ||
42 | CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034 | ||
43 | CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035 | ||
44 | CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036 | ||
45 | CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037 | ||
46 | CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038 | ||
47 | CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039 | ||
48 | CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040 | ||
49 | CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041 | ||
50 | CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042 | ||
51 | CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043 | ||
52 | CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044 | ||
53 | CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045 | ||
54 | CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046 | ||
55 | CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047 | ||
56 | CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048 | ||
57 | CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049 | ||
58 | CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050 | ||
59 | CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051 | ||
60 | CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052 | ||
61 | CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053 | ||
62 | CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054 | ||
63 | CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055 | ||
64 | CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056 | ||
65 | CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057 | ||
66 | CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058 | ||
67 | CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059 | ||
68 | CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060 | ||
69 | CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061 | ||
70 | CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062 | ||
71 | CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063 | ||
72 | CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064 | ||
73 | CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065 | ||
74 | CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066 | ||
75 | CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067 | ||
76 | CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068 | ||
77 | CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069 | ||
78 | CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070 | ||
79 | CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071 | ||
80 | CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072 | ||
81 | CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073 | ||
82 | CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074 | ||
83 | CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075 | ||
84 | CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076 | ||
85 | CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077 | ||
86 | CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078 | ||
87 | CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079 | ||
88 | CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080 | ||
89 | CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081 | ||
90 | CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082 | ||
91 | CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083 | ||
92 | CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084 | ||
93 | CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085 | ||
94 | CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086 | ||
95 | CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087 | ||
96 | CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088 | ||
97 | CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089 | ||
98 | CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090 | ||
99 | CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091 | ||
100 | CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092 | ||
101 | CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093 | ||
102 | CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094 | ||
103 | CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095 | ||
104 | CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096 | ||
105 | CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097 | ||
106 | CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098 | ||
107 | CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099 | ||
108 | CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100 | ||
109 | CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101 | ||
110 | CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102 | ||
111 | CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103 | ||
112 | CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104 | ||
113 | CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105 | ||
114 | CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106 | ||
115 | CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107 | ||
116 | CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108 | ||
117 | CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109 | ||
118 | CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110 | ||
119 | CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111 | ||
120 | CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112 | ||
121 | CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113 | ||
122 | CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114 | ||
123 | CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115 | ||
124 | CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116 | ||
125 | CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117 | ||
126 | CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118 | ||
127 | CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119 | ||
128 | CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120 | ||
129 | CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121 | ||
130 | CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122 | ||
131 | CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123 | ||
132 | CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124 | ||
133 | CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125 | ||
134 | CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126 | ||
135 | CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127 | ||
136 | |||
137 | zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc | ||
138 | zzzInitializeEmbeddedUI=InitializeEmbeddedUI | ||
139 | zzzEmbeddedUIHandler=EmbeddedUIHandler | ||
140 | zzzShutdownEmbeddedUI=ShutdownEmbeddedUI | ||
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 | |||
3 | int InvokeCustomAction(MSIHANDLE hSession, | ||
4 | const wchar_t* szWorkingDir, const wchar_t* szEntryPoint); | ||
5 | |||
6 | /// <summary> | ||
7 | /// Macro for defining and exporting a custom action entrypoint. | ||
8 | /// </summary> | ||
9 | /// <param name="name">Name of the entrypoint as exported from | ||
10 | /// the DLL.</param> | ||
11 | /// <param name="method">Path to the managed custom action method, | ||
12 | /// in the form: "AssemblyName!Namespace.Class.Method"</param> | ||
13 | /// <remarks> | ||
14 | /// To prevent the exported name from being decorated, add | ||
15 | /// /EXPORT:name to the linker options for every entrypoint. | ||
16 | /// </remarks> | ||
17 | #define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \ | ||
18 | name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); } | ||
19 | |||
20 | // TEMPLATE ENTRYPOINTS | ||
21 | // To be edited by the MakeSfxCA tool. | ||
22 | |||
23 | #define NULLSPACE \ | ||
24 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
25 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
26 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \ | ||
27 | L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
28 | |||
29 | #define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \ | ||
30 | CustomActionEntryPoint##id##, \ | ||
31 | L"CustomActionEntryPoint" sid NULLSPACE) | ||
32 | |||
33 | TEMPLATE_CA_ENTRYPOINT(000,L"000"); | ||
34 | TEMPLATE_CA_ENTRYPOINT(001,L"001"); | ||
35 | TEMPLATE_CA_ENTRYPOINT(002,L"002"); | ||
36 | TEMPLATE_CA_ENTRYPOINT(003,L"003"); | ||
37 | TEMPLATE_CA_ENTRYPOINT(004,L"004"); | ||
38 | TEMPLATE_CA_ENTRYPOINT(005,L"005"); | ||
39 | TEMPLATE_CA_ENTRYPOINT(006,L"006"); | ||
40 | TEMPLATE_CA_ENTRYPOINT(007,L"007"); | ||
41 | TEMPLATE_CA_ENTRYPOINT(008,L"008"); | ||
42 | TEMPLATE_CA_ENTRYPOINT(009,L"009"); | ||
43 | TEMPLATE_CA_ENTRYPOINT(010,L"010"); | ||
44 | TEMPLATE_CA_ENTRYPOINT(011,L"011"); | ||
45 | TEMPLATE_CA_ENTRYPOINT(012,L"012"); | ||
46 | TEMPLATE_CA_ENTRYPOINT(013,L"013"); | ||
47 | TEMPLATE_CA_ENTRYPOINT(014,L"014"); | ||
48 | TEMPLATE_CA_ENTRYPOINT(015,L"015"); | ||
49 | TEMPLATE_CA_ENTRYPOINT(016,L"016"); | ||
50 | TEMPLATE_CA_ENTRYPOINT(017,L"017"); | ||
51 | TEMPLATE_CA_ENTRYPOINT(018,L"018"); | ||
52 | TEMPLATE_CA_ENTRYPOINT(019,L"019"); | ||
53 | TEMPLATE_CA_ENTRYPOINT(020,L"020"); | ||
54 | TEMPLATE_CA_ENTRYPOINT(021,L"021"); | ||
55 | TEMPLATE_CA_ENTRYPOINT(022,L"022"); | ||
56 | TEMPLATE_CA_ENTRYPOINT(023,L"023"); | ||
57 | TEMPLATE_CA_ENTRYPOINT(024,L"024"); | ||
58 | TEMPLATE_CA_ENTRYPOINT(025,L"025"); | ||
59 | TEMPLATE_CA_ENTRYPOINT(026,L"026"); | ||
60 | TEMPLATE_CA_ENTRYPOINT(027,L"027"); | ||
61 | TEMPLATE_CA_ENTRYPOINT(028,L"028"); | ||
62 | TEMPLATE_CA_ENTRYPOINT(029,L"029"); | ||
63 | TEMPLATE_CA_ENTRYPOINT(030,L"030"); | ||
64 | TEMPLATE_CA_ENTRYPOINT(031,L"031"); | ||
65 | TEMPLATE_CA_ENTRYPOINT(032,L"032"); | ||
66 | TEMPLATE_CA_ENTRYPOINT(033,L"033"); | ||
67 | TEMPLATE_CA_ENTRYPOINT(034,L"034"); | ||
68 | TEMPLATE_CA_ENTRYPOINT(035,L"035"); | ||
69 | TEMPLATE_CA_ENTRYPOINT(036,L"036"); | ||
70 | TEMPLATE_CA_ENTRYPOINT(037,L"037"); | ||
71 | TEMPLATE_CA_ENTRYPOINT(038,L"038"); | ||
72 | TEMPLATE_CA_ENTRYPOINT(039,L"039"); | ||
73 | TEMPLATE_CA_ENTRYPOINT(040,L"040"); | ||
74 | TEMPLATE_CA_ENTRYPOINT(041,L"041"); | ||
75 | TEMPLATE_CA_ENTRYPOINT(042,L"042"); | ||
76 | TEMPLATE_CA_ENTRYPOINT(043,L"043"); | ||
77 | TEMPLATE_CA_ENTRYPOINT(044,L"044"); | ||
78 | TEMPLATE_CA_ENTRYPOINT(045,L"045"); | ||
79 | TEMPLATE_CA_ENTRYPOINT(046,L"046"); | ||
80 | TEMPLATE_CA_ENTRYPOINT(047,L"047"); | ||
81 | TEMPLATE_CA_ENTRYPOINT(048,L"048"); | ||
82 | TEMPLATE_CA_ENTRYPOINT(049,L"049"); | ||
83 | TEMPLATE_CA_ENTRYPOINT(050,L"050"); | ||
84 | TEMPLATE_CA_ENTRYPOINT(051,L"051"); | ||
85 | TEMPLATE_CA_ENTRYPOINT(052,L"052"); | ||
86 | TEMPLATE_CA_ENTRYPOINT(053,L"053"); | ||
87 | TEMPLATE_CA_ENTRYPOINT(054,L"054"); | ||
88 | TEMPLATE_CA_ENTRYPOINT(055,L"055"); | ||
89 | TEMPLATE_CA_ENTRYPOINT(056,L"056"); | ||
90 | TEMPLATE_CA_ENTRYPOINT(057,L"057"); | ||
91 | TEMPLATE_CA_ENTRYPOINT(058,L"058"); | ||
92 | TEMPLATE_CA_ENTRYPOINT(059,L"059"); | ||
93 | TEMPLATE_CA_ENTRYPOINT(060,L"060"); | ||
94 | TEMPLATE_CA_ENTRYPOINT(061,L"061"); | ||
95 | TEMPLATE_CA_ENTRYPOINT(062,L"062"); | ||
96 | TEMPLATE_CA_ENTRYPOINT(063,L"063"); | ||
97 | TEMPLATE_CA_ENTRYPOINT(064,L"064"); | ||
98 | TEMPLATE_CA_ENTRYPOINT(065,L"065"); | ||
99 | TEMPLATE_CA_ENTRYPOINT(066,L"066"); | ||
100 | TEMPLATE_CA_ENTRYPOINT(067,L"067"); | ||
101 | TEMPLATE_CA_ENTRYPOINT(068,L"068"); | ||
102 | TEMPLATE_CA_ENTRYPOINT(069,L"069"); | ||
103 | TEMPLATE_CA_ENTRYPOINT(070,L"070"); | ||
104 | TEMPLATE_CA_ENTRYPOINT(071,L"071"); | ||
105 | TEMPLATE_CA_ENTRYPOINT(072,L"072"); | ||
106 | TEMPLATE_CA_ENTRYPOINT(073,L"073"); | ||
107 | TEMPLATE_CA_ENTRYPOINT(074,L"074"); | ||
108 | TEMPLATE_CA_ENTRYPOINT(075,L"075"); | ||
109 | TEMPLATE_CA_ENTRYPOINT(076,L"076"); | ||
110 | TEMPLATE_CA_ENTRYPOINT(077,L"077"); | ||
111 | TEMPLATE_CA_ENTRYPOINT(078,L"078"); | ||
112 | TEMPLATE_CA_ENTRYPOINT(079,L"079"); | ||
113 | TEMPLATE_CA_ENTRYPOINT(080,L"080"); | ||
114 | TEMPLATE_CA_ENTRYPOINT(081,L"081"); | ||
115 | TEMPLATE_CA_ENTRYPOINT(082,L"082"); | ||
116 | TEMPLATE_CA_ENTRYPOINT(083,L"083"); | ||
117 | TEMPLATE_CA_ENTRYPOINT(084,L"084"); | ||
118 | TEMPLATE_CA_ENTRYPOINT(085,L"085"); | ||
119 | TEMPLATE_CA_ENTRYPOINT(086,L"086"); | ||
120 | TEMPLATE_CA_ENTRYPOINT(087,L"087"); | ||
121 | TEMPLATE_CA_ENTRYPOINT(088,L"088"); | ||
122 | TEMPLATE_CA_ENTRYPOINT(089,L"089"); | ||
123 | TEMPLATE_CA_ENTRYPOINT(090,L"090"); | ||
124 | TEMPLATE_CA_ENTRYPOINT(091,L"091"); | ||
125 | TEMPLATE_CA_ENTRYPOINT(092,L"092"); | ||
126 | TEMPLATE_CA_ENTRYPOINT(093,L"093"); | ||
127 | TEMPLATE_CA_ENTRYPOINT(094,L"094"); | ||
128 | TEMPLATE_CA_ENTRYPOINT(095,L"095"); | ||
129 | TEMPLATE_CA_ENTRYPOINT(096,L"096"); | ||
130 | TEMPLATE_CA_ENTRYPOINT(097,L"097"); | ||
131 | TEMPLATE_CA_ENTRYPOINT(098,L"098"); | ||
132 | TEMPLATE_CA_ENTRYPOINT(099,L"099"); | ||
133 | TEMPLATE_CA_ENTRYPOINT(100,L"100"); | ||
134 | TEMPLATE_CA_ENTRYPOINT(101,L"101"); | ||
135 | TEMPLATE_CA_ENTRYPOINT(102,L"102"); | ||
136 | TEMPLATE_CA_ENTRYPOINT(103,L"103"); | ||
137 | TEMPLATE_CA_ENTRYPOINT(104,L"104"); | ||
138 | TEMPLATE_CA_ENTRYPOINT(105,L"105"); | ||
139 | TEMPLATE_CA_ENTRYPOINT(106,L"106"); | ||
140 | TEMPLATE_CA_ENTRYPOINT(107,L"107"); | ||
141 | TEMPLATE_CA_ENTRYPOINT(108,L"108"); | ||
142 | TEMPLATE_CA_ENTRYPOINT(109,L"109"); | ||
143 | TEMPLATE_CA_ENTRYPOINT(110,L"110"); | ||
144 | TEMPLATE_CA_ENTRYPOINT(111,L"111"); | ||
145 | TEMPLATE_CA_ENTRYPOINT(112,L"112"); | ||
146 | TEMPLATE_CA_ENTRYPOINT(113,L"113"); | ||
147 | TEMPLATE_CA_ENTRYPOINT(114,L"114"); | ||
148 | TEMPLATE_CA_ENTRYPOINT(115,L"115"); | ||
149 | TEMPLATE_CA_ENTRYPOINT(116,L"116"); | ||
150 | TEMPLATE_CA_ENTRYPOINT(117,L"117"); | ||
151 | TEMPLATE_CA_ENTRYPOINT(118,L"118"); | ||
152 | TEMPLATE_CA_ENTRYPOINT(119,L"119"); | ||
153 | TEMPLATE_CA_ENTRYPOINT(120,L"120"); | ||
154 | TEMPLATE_CA_ENTRYPOINT(121,L"121"); | ||
155 | TEMPLATE_CA_ENTRYPOINT(122,L"122"); | ||
156 | TEMPLATE_CA_ENTRYPOINT(123,L"123"); | ||
157 | TEMPLATE_CA_ENTRYPOINT(124,L"124"); | ||
158 | TEMPLATE_CA_ENTRYPOINT(125,L"125"); | ||
159 | TEMPLATE_CA_ENTRYPOINT(126,L"126"); | ||
160 | TEMPLATE_CA_ENTRYPOINT(127,L"127"); | ||
161 | |||
162 | // Note: Keep in sync with EntryPoints.def | ||
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. | ||
13 | static HFDI g_hfdi; | ||
14 | |||
15 | // FDI is not unicode-aware, so avoid passing these paths through the callbacks. | ||
16 | static const wchar_t* g_szExtractDir; | ||
17 | static const wchar_t* g_szCabFile; | ||
18 | |||
19 | // Offset into the source file where the cabinet really starts. | ||
20 | // Used to trick FDI into extracting from a concatenated cabinet. | ||
21 | static int g_lCabOffset; | ||
22 | |||
23 | // Use the secure CRT version of _wsopen if available. | ||
24 | #ifdef __GOT_SECURE_LIB__ | ||
25 | #define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) | ||
26 | #else | ||
27 | #define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) | ||
28 | #endif | ||
29 | |||
30 | /// <summary> | ||
31 | /// FDI callback to open a cabinet file. | ||
32 | /// </summary> | ||
33 | /// <param name="pszFile">Name of the file to be opened. This parameter | ||
34 | /// is ignored since with our limited use this method is only ever called | ||
35 | /// to open the main cabinet file.</param> | ||
36 | /// <param name="oflag">Type of operations allowed.</param> | ||
37 | /// <param name="pmode">Permission setting.</param> | ||
38 | /// <returns>Integer file handle, or -1 if the file could not be opened.</returns> | ||
39 | /// <remarks> | ||
40 | /// To support reading from a cabinet that is concatenated onto | ||
41 | /// another file, this function first searches for the offset of the cabinet, | ||
42 | /// then saves that offset for use in recalculating later seeks. | ||
43 | /// </remarks> | ||
44 | static FNOPEN(CabOpen) | ||
45 | { | ||
46 | UNREFERENCED_PARAMETER(pszFile); | ||
47 | int hf; | ||
48 | _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); | ||
49 | if (hf != -1) | ||
50 | { | ||
51 | FDICABINETINFO cabInfo; | ||
52 | int length = _lseek(hf, 0, SEEK_END); | ||
53 | for(int offset = 0; offset < length; offset += 256) | ||
54 | { | ||
55 | if (_lseek(hf, offset, SEEK_SET) != offset) break; | ||
56 | if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) | ||
57 | { | ||
58 | g_lCabOffset = offset; | ||
59 | _lseek(hf, offset, SEEK_SET); | ||
60 | return hf; | ||
61 | } | ||
62 | } | ||
63 | _close(hf); | ||
64 | } | ||
65 | return -1; | ||
66 | } | ||
67 | |||
68 | /// <summary> | ||
69 | /// FDI callback to seek within a file. | ||
70 | /// </summary> | ||
71 | /// <param name="hf">File handle.</param> | ||
72 | /// <param name="dist">Seek distance</param> | ||
73 | /// <param name="seektype">Whether to seek relative to the | ||
74 | /// beginning, current position, or end of the file.</param> | ||
75 | /// <returns>Resultant position within the cabinet.</returns> | ||
76 | /// <remarks> | ||
77 | /// To support reading from a cabinet that is concatenated onto | ||
78 | /// another file, this function recalculates seeks based on the | ||
79 | /// offset that was determined when the cabinet was opened. | ||
80 | /// </remarks> | ||
81 | static FNSEEK(CabSeek) | ||
82 | { | ||
83 | if (seektype == SEEK_SET) dist += g_lCabOffset; | ||
84 | int pos = _lseek((int) hf, dist, seektype); | ||
85 | pos -= g_lCabOffset; | ||
86 | return pos; | ||
87 | } | ||
88 | |||
89 | /// <summary> | ||
90 | /// Ensures a directory and its parent directory path exists. | ||
91 | /// </summary> | ||
92 | /// <param name="szDirPath">Directory path, not including file name.</param> | ||
93 | /// <returns>0 if the directory exists or was successfully created, else nonzero.</returns> | ||
94 | /// <remarks> | ||
95 | /// This function modifies characters in szDirPath, but always restores them | ||
96 | /// regardless of error condition. | ||
97 | /// </remarks> | ||
98 | static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) | ||
99 | { | ||
100 | int ret = 0; | ||
101 | if (!::CreateDirectoryW(szDirPath, NULL)) | ||
102 | { | ||
103 | UINT err = ::GetLastError(); | ||
104 | if (err != ERROR_ALREADY_EXISTS) | ||
105 | { | ||
106 | // Directory creation failed for some reason other than already existing. | ||
107 | // Try to create the parent directory first. | ||
108 | wchar_t* szLastSlash = NULL; | ||
109 | for (wchar_t* sz = szDirPath; *sz; sz++) | ||
110 | { | ||
111 | if (*sz == L'\\') | ||
112 | { | ||
113 | szLastSlash = sz; | ||
114 | } | ||
115 | } | ||
116 | if (szLastSlash) | ||
117 | { | ||
118 | // Temporarily take one directory off the path and recurse. | ||
119 | *szLastSlash = L'\0'; | ||
120 | ret = EnsureDirectoryExists(szDirPath); | ||
121 | *szLastSlash = L'\\'; | ||
122 | |||
123 | // Try to create the directory if all parents are created. | ||
124 | if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) | ||
125 | { | ||
126 | err = ::GetLastError(); | ||
127 | if (err != ERROR_ALREADY_EXISTS) | ||
128 | { | ||
129 | ret = -1; | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | else | ||
134 | { | ||
135 | ret = -1; | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | return ret; | ||
140 | } | ||
141 | |||
142 | /// <summary> | ||
143 | /// Ensures a file's directory and its parent directory path exists. | ||
144 | /// </summary> | ||
145 | /// <param name="szDirPath">Path including file name.</param> | ||
146 | /// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns> | ||
147 | /// <remarks> | ||
148 | /// This function modifies characters in szFilePath, but always restores them | ||
149 | /// regardless of error condition. | ||
150 | /// </remarks> | ||
151 | static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) | ||
152 | { | ||
153 | int ret = 0; | ||
154 | wchar_t* szLastSlash = NULL; | ||
155 | for (wchar_t* sz = szFilePath; *sz; sz++) | ||
156 | { | ||
157 | if (*sz == L'\\') | ||
158 | { | ||
159 | szLastSlash = sz; | ||
160 | } | ||
161 | } | ||
162 | if (szLastSlash) | ||
163 | { | ||
164 | *szLastSlash = L'\0'; | ||
165 | ret = EnsureDirectoryExists(szFilePath); | ||
166 | *szLastSlash = L'\\'; | ||
167 | } | ||
168 | return ret; | ||
169 | } | ||
170 | |||
171 | /// <summary> | ||
172 | /// FDI callback for handling files in the cabinet. | ||
173 | /// </summary> | ||
174 | /// <param name="fdint">Type of notification.</param> | ||
175 | /// <param name="pfdin">Structure containing data about the notification.</param> | ||
176 | /// <remarks> | ||
177 | /// Refer to fdi.h for more comments on this notification callback. | ||
178 | /// </remarks> | ||
179 | static FNFDINOTIFY(CabNotification) | ||
180 | { | ||
181 | // fdintCOPY_FILE: | ||
182 | // Called for each file that *starts* in the current cabinet, giving | ||
183 | // the client the opportunity to request that the file be copied or | ||
184 | // skipped. | ||
185 | // Entry: | ||
186 | // pfdin->psz1 = file name in cabinet | ||
187 | // pfdin->cb = uncompressed size of file | ||
188 | // pfdin->date = file date | ||
189 | // pfdin->time = file time | ||
190 | // pfdin->attribs = file attributes | ||
191 | // pfdin->iFolder = file's folder index | ||
192 | // Exit-Success: | ||
193 | // Return non-zero file handle for destination file; FDI writes | ||
194 | // data to this file use the PFNWRITE function supplied to FDICreate, | ||
195 | // and then calls fdintCLOSE_FILE_INFO to close the file and set | ||
196 | // the date, time, and attributes. | ||
197 | // Exit-Failure: | ||
198 | // Returns 0 => Skip file, do not copy | ||
199 | // Returns -1 => Abort FDICopy() call | ||
200 | if (fdint == fdintCOPY_FILE) | ||
201 | { | ||
202 | size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); | ||
203 | size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; | ||
204 | wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); | ||
205 | if (szFilePath == NULL) return -1; | ||
206 | StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); | ||
207 | StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); | ||
208 | MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, | ||
209 | szFilePath + cchFilePath - cchFile, (int) cchFile + 1); | ||
210 | int hf = -1; | ||
211 | if (EnsureFileDirectoryExists(szFilePath) == 0) | ||
212 | { | ||
213 | _wsopen__s(hf, szFilePath, | ||
214 | _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, | ||
215 | _SH_DENYWR, _S_IREAD | _S_IWRITE); | ||
216 | } | ||
217 | return hf; | ||
218 | } | ||
219 | |||
220 | // fdintCLOSE_FILE_INFO: | ||
221 | // Called after all of the data has been written to a target file. | ||
222 | // This function must close the file and set the file date, time, | ||
223 | // and attributes. | ||
224 | // Entry: | ||
225 | // pfdin->psz1 = file name in cabinet | ||
226 | // pfdin->hf = file handle | ||
227 | // pfdin->date = file date | ||
228 | // pfdin->time = file time | ||
229 | // pfdin->attribs = file attributes | ||
230 | // pfdin->iFolder = file's folder index | ||
231 | // pfdin->cb = Run After Extract (0 - don't run, 1 Run) | ||
232 | // Exit-Success: | ||
233 | // Returns TRUE | ||
234 | // Exit-Failure: | ||
235 | // Returns FALSE, or -1 to abort | ||
236 | else if (fdint == fdintCLOSE_FILE_INFO) | ||
237 | { | ||
238 | _close((int) pfdin->hf); | ||
239 | return TRUE; | ||
240 | } | ||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | /// <summary> | ||
245 | /// Extracts all contents of a cabinet file to a directory. | ||
246 | /// </summary> | ||
247 | /// <param name="szCabFile">Path to the cabinet file to be extracted. | ||
248 | /// The cabinet may actually start at some offset within the file, | ||
249 | /// as long as that offset is a multiple of 256.</param> | ||
250 | /// <param name="szExtractDir">Directory where files are to be extracted. | ||
251 | /// This directory must already exist, but should be empty.</param> | ||
252 | /// <returns>0 if the cabinet was extracted successfully, | ||
253 | /// or an error code if any error occurred.</returns> | ||
254 | /// <remarks> | ||
255 | /// The extraction will not overwrite any files in the destination | ||
256 | /// directory; extraction will be interrupted and fail if any files | ||
257 | /// with the same name already exist. | ||
258 | /// </remarks> | ||
259 | int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) | ||
260 | { | ||
261 | ERF erf; | ||
262 | // Most of the FDI callbacks can be handled by existing CRT I/O functions. | ||
263 | // For our functionality we only need to handle the open and seek callbacks. | ||
264 | HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, | ||
265 | (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, | ||
266 | CabSeek, cpu80386, &erf); | ||
267 | if (hfdi != NULL) | ||
268 | { | ||
269 | g_hfdi = hfdi; | ||
270 | g_szCabFile = szCabFile; | ||
271 | g_szExtractDir = szExtractDir; | ||
272 | char szEmpty[1] = {0}; | ||
273 | if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) | ||
274 | { | ||
275 | FDIDestroy(hfdi); | ||
276 | return 0; | ||
277 | } | ||
278 | FDIDestroy(hfdi); | ||
279 | } | ||
280 | |||
281 | return erf.erfOper; | ||
282 | } | ||
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 | // | ||
12 | static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired) | ||
13 | { | ||
14 | // It will also reduce the buffer size if the previous allocation was very large. | ||
15 | if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf)) | ||
16 | { | ||
17 | if (*pszBuf != NULL) | ||
18 | { | ||
19 | SecureZeroMemory(*pszBuf, *pcchBuf); | ||
20 | delete[] *pszBuf; | ||
21 | } | ||
22 | |||
23 | *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired); | ||
24 | *pszBuf = new wchar_t[*pcchBuf]; | ||
25 | |||
26 | if (*pszBuf == NULL) | ||
27 | { | ||
28 | return ERROR_OUTOFMEMORY; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | return ERROR_SUCCESS; | ||
33 | } | ||
34 | |||
35 | typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1); | ||
36 | typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1); | ||
37 | typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1); | ||
38 | typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1); | ||
39 | typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1); | ||
40 | typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2); | ||
41 | typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
42 | typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
43 | typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
44 | typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1); | ||
45 | typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3); | ||
46 | |||
47 | UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
48 | { | ||
49 | int in1 = pReq->fields[0].iValue; | ||
50 | int out1; | ||
51 | UINT ret = (UINT) func(in1, &out1); | ||
52 | if (ret == 0) | ||
53 | { | ||
54 | pResp->fields[1].vt = VT_I4; | ||
55 | pResp->fields[1].iValue = out1; | ||
56 | } | ||
57 | return ret; | ||
58 | } | ||
59 | |||
60 | UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
61 | { | ||
62 | int in1 = pReq->fields[0].iValue; | ||
63 | int in2 = pReq->fields[1].iValue; | ||
64 | int out1; | ||
65 | UINT ret = (UINT) func(in1, in2, &out1); | ||
66 | if (ret == 0) | ||
67 | { | ||
68 | pResp->fields[1].vt = VT_I4; | ||
69 | pResp->fields[1].iValue = out1; | ||
70 | } | ||
71 | return ret; | ||
72 | } | ||
73 | |||
74 | UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
75 | { | ||
76 | int in1 = pReq->fields[0].iValue; | ||
77 | wchar_t* in2 = pReq->fields[1].szValue; | ||
78 | int out1; | ||
79 | UINT ret = (UINT) func(in1, in2, &out1); | ||
80 | if (ret == 0) | ||
81 | { | ||
82 | pResp->fields[1].vt = VT_I4; | ||
83 | pResp->fields[1].iValue = out1; | ||
84 | } | ||
85 | return ret; | ||
86 | } | ||
87 | |||
88 | UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
89 | { | ||
90 | int in1 = pReq->fields[0].iValue; | ||
91 | wchar_t* in2 = pReq->fields[1].szValue; | ||
92 | int in3 = pReq->fields[2].iValue; | ||
93 | int out1; | ||
94 | UINT ret = (UINT) func(in1, in2, in3, &out1); | ||
95 | if (ret == 0) | ||
96 | { | ||
97 | pResp->fields[1].vt = VT_I4; | ||
98 | pResp->fields[1].iValue = out1; | ||
99 | } | ||
100 | return ret; | ||
101 | } | ||
102 | |||
103 | UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
104 | { | ||
105 | int in1 = pReq->fields[0].iValue; | ||
106 | wchar_t* in2 = pReq->fields[1].szValue; | ||
107 | int in3 = pReq->fields[2].iValue; | ||
108 | int in4 = pReq->fields[3].iValue; | ||
109 | int out1; | ||
110 | UINT ret = (UINT) func(in1, in2, in3, in4, &out1); | ||
111 | if (ret == 0) | ||
112 | { | ||
113 | pResp->fields[1].vt = VT_I4; | ||
114 | pResp->fields[1].iValue = out1; | ||
115 | } | ||
116 | return ret; | ||
117 | } | ||
118 | |||
119 | UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp) | ||
120 | { | ||
121 | int in1 = pReq->fields[0].iValue; | ||
122 | wchar_t* in2 = pReq->fields[1].szValue; | ||
123 | int out1, out2; | ||
124 | UINT ret = (UINT) func(in1, in2, &out1, &out2); | ||
125 | if (ret == 0) | ||
126 | { | ||
127 | pResp->fields[1].vt = VT_I4; | ||
128 | pResp->fields[1].iValue = out1; | ||
129 | pResp->fields[2].vt = VT_I4; | ||
130 | pResp->fields[2].iValue = out2; | ||
131 | } | ||
132 | return ret; | ||
133 | } | ||
134 | |||
135 | UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
136 | { | ||
137 | int in1 = pReq->fields[0].iValue; | ||
138 | szBuf[0] = L'\0'; | ||
139 | DWORD cchValue = cchBuf; | ||
140 | UINT ret = (UINT) func(in1, szBuf, &cchValue); | ||
141 | if (ret == ERROR_MORE_DATA) | ||
142 | { | ||
143 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
144 | if (ret == 0) | ||
145 | { | ||
146 | ret = (UINT) func(in1, szBuf, &cchValue); | ||
147 | } | ||
148 | } | ||
149 | if (ret == 0) | ||
150 | { | ||
151 | pResp->fields[1].vt = VT_LPWSTR; | ||
152 | pResp->fields[1].szValue = szBuf; | ||
153 | } | ||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
158 | { | ||
159 | int in1 = pReq->fields[0].iValue; | ||
160 | szBuf[0] = L'\0'; | ||
161 | DWORD cchValue = cchBuf; | ||
162 | MSIDBERROR ret = func(in1, szBuf, &cchValue); | ||
163 | if (ret == MSIDBERROR_MOREDATA) | ||
164 | { | ||
165 | if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue)) | ||
166 | { | ||
167 | ret = func(in1, szBuf, &cchValue); | ||
168 | } | ||
169 | } | ||
170 | if (ret != MSIDBERROR_MOREDATA) | ||
171 | { | ||
172 | pResp->fields[1].vt = VT_LPWSTR; | ||
173 | pResp->fields[1].szValue = szBuf; | ||
174 | } | ||
175 | return ret; | ||
176 | } | ||
177 | |||
178 | UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
179 | { | ||
180 | int in1 = pReq->fields[0].iValue; | ||
181 | int in2 = pReq->fields[1].iValue; | ||
182 | szBuf[0] = L'\0'; | ||
183 | DWORD cchValue = cchBuf; | ||
184 | UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
185 | if (ret == ERROR_MORE_DATA) | ||
186 | { | ||
187 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
188 | if (ret == 0) | ||
189 | { | ||
190 | ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
191 | } | ||
192 | } | ||
193 | if (ret == 0) | ||
194 | { | ||
195 | pResp->fields[1].vt = VT_LPWSTR; | ||
196 | pResp->fields[1].szValue = szBuf; | ||
197 | } | ||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
202 | { | ||
203 | int in1 = pReq->fields[0].iValue; | ||
204 | wchar_t* in2 = pReq->fields[1].szValue; | ||
205 | szBuf[0] = L'\0'; | ||
206 | DWORD cchValue = cchBuf; | ||
207 | UINT ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
208 | if (ret == ERROR_MORE_DATA) | ||
209 | { | ||
210 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
211 | if (ret == 0) | ||
212 | { | ||
213 | ret = (UINT) func(in1, in2, szBuf, &cchValue); | ||
214 | } | ||
215 | } | ||
216 | if (ret == 0) | ||
217 | { | ||
218 | pResp->fields[1].vt = VT_LPWSTR; | ||
219 | pResp->fields[1].szValue = szBuf; | ||
220 | } | ||
221 | return ret; | ||
222 | } | ||
223 | |||
224 | UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf) | ||
225 | { | ||
226 | int in1 = pReq->fields[0].iValue; | ||
227 | wchar_t* in2 = pReq->fields[1].szValue; | ||
228 | int in3 = pReq->fields[2].iValue; | ||
229 | int in4 = pReq->fields[3].iValue; | ||
230 | szBuf[0] = L'\0'; | ||
231 | DWORD cchValue = cchBuf; | ||
232 | int out2, out3; | ||
233 | UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); | ||
234 | if (ret == ERROR_MORE_DATA) | ||
235 | { | ||
236 | ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue); | ||
237 | if (ret == 0) | ||
238 | { | ||
239 | ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3); | ||
240 | } | ||
241 | } | ||
242 | if (ret == 0) | ||
243 | { | ||
244 | pResp->fields[1].vt = VT_LPWSTR; | ||
245 | pResp->fields[1].szValue = szBuf; | ||
246 | pResp->fields[2].vt = VT_I4; | ||
247 | pResp->fields[2].iValue = out2; | ||
248 | pResp->fields[3].vt = VT_I4; | ||
249 | pResp->fields[3].iValue = out3; | ||
250 | } | ||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp) | ||
255 | { | ||
256 | SecureZeroMemory(pResp, sizeof(RequestData)); | ||
257 | |||
258 | UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024); | ||
259 | |||
260 | if (0 == ret) | ||
261 | { | ||
262 | switch (id) | ||
263 | { | ||
264 | case RemoteMsiSession::EndSession: | ||
265 | { | ||
266 | this->ExitCode = pReq->fields[0].iValue; | ||
267 | } | ||
268 | break; | ||
269 | case RemoteMsiSession::MsiCloseHandle: | ||
270 | { | ||
271 | MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue; | ||
272 | ret = ::MsiCloseHandle(h); | ||
273 | } | ||
274 | break; | ||
275 | case RemoteMsiSession::MsiProcessMessage: | ||
276 | { | ||
277 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
278 | INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue; | ||
279 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; | ||
280 | ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord); | ||
281 | } | ||
282 | break; | ||
283 | case RemoteMsiSession::MsiGetProperty: | ||
284 | { | ||
285 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
286 | } | ||
287 | break; | ||
288 | case RemoteMsiSession::MsiSetProperty: | ||
289 | { | ||
290 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
291 | const wchar_t* szName = pReq->fields[1].szValue; | ||
292 | const wchar_t* szValue = pReq->fields[2].szValue; | ||
293 | ret = ::MsiSetProperty(hInstall, szName, szValue); | ||
294 | } | ||
295 | break; | ||
296 | case RemoteMsiSession::MsiCreateRecord: | ||
297 | { | ||
298 | UINT cParams = pReq->fields[0].uiValue; | ||
299 | ret = ::MsiCreateRecord(cParams); | ||
300 | } | ||
301 | break; | ||
302 | case RemoteMsiSession::MsiRecordGetFieldCount: | ||
303 | { | ||
304 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
305 | ret = ::MsiRecordGetFieldCount(hRecord); | ||
306 | } | ||
307 | break; | ||
308 | case RemoteMsiSession::MsiRecordGetInteger: | ||
309 | { | ||
310 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
311 | UINT iField = pReq->fields[1].uiValue; | ||
312 | ret = ::MsiRecordGetInteger(hRecord, iField); | ||
313 | } | ||
314 | break; | ||
315 | case RemoteMsiSession::MsiRecordSetInteger: | ||
316 | { | ||
317 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
318 | UINT iField = pReq->fields[1].uiValue; | ||
319 | int iValue = pReq->fields[2].iValue; | ||
320 | ret = ::MsiRecordSetInteger(hRecord, iField, iValue); | ||
321 | } | ||
322 | break; | ||
323 | case RemoteMsiSession::MsiRecordGetString: | ||
324 | { | ||
325 | ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
326 | } | ||
327 | break; | ||
328 | case RemoteMsiSession::MsiRecordSetString: | ||
329 | { | ||
330 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
331 | UINT iField = pReq->fields[1].uiValue; | ||
332 | const wchar_t* szValue = pReq->fields[2].szValue; | ||
333 | ret = ::MsiRecordSetString(hRecord, iField, szValue); | ||
334 | } | ||
335 | break; | ||
336 | case RemoteMsiSession::MsiRecordClearData: | ||
337 | { | ||
338 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
339 | ret = ::MsiRecordClearData(hRecord); | ||
340 | } | ||
341 | break; | ||
342 | case RemoteMsiSession::MsiRecordIsNull: | ||
343 | { | ||
344 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
345 | UINT iField = pReq->fields[1].uiValue; | ||
346 | ret = ::MsiRecordIsNull(hRecord, iField); | ||
347 | } | ||
348 | break; | ||
349 | case RemoteMsiSession::MsiFormatRecord: | ||
350 | { | ||
351 | ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
352 | } | ||
353 | break; | ||
354 | case RemoteMsiSession::MsiGetActiveDatabase: | ||
355 | { | ||
356 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
357 | ret = (UINT) ::MsiGetActiveDatabase(hInstall); | ||
358 | } | ||
359 | break; | ||
360 | case RemoteMsiSession::MsiDatabaseOpenView: | ||
361 | { | ||
362 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp); | ||
363 | } | ||
364 | break; | ||
365 | case RemoteMsiSession::MsiViewExecute: | ||
366 | { | ||
367 | MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; | ||
368 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue; | ||
369 | ret = ::MsiViewExecute(hView, hRecord); | ||
370 | } | ||
371 | break; | ||
372 | case RemoteMsiSession::MsiViewFetch: | ||
373 | { | ||
374 | ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp); | ||
375 | } | ||
376 | break; | ||
377 | case RemoteMsiSession::MsiViewModify: | ||
378 | { | ||
379 | MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue; | ||
380 | MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue; | ||
381 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue; | ||
382 | ret = ::MsiViewModify(hView, eModifyMode, hRecord); | ||
383 | } | ||
384 | break; | ||
385 | case RemoteMsiSession::MsiViewGetError: | ||
386 | { | ||
387 | ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
388 | } | ||
389 | break; | ||
390 | case RemoteMsiSession::MsiViewGetColumnInfo: | ||
391 | { | ||
392 | ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp); | ||
393 | } | ||
394 | break; | ||
395 | case RemoteMsiSession::MsiDatabaseGetPrimaryKeys: | ||
396 | { | ||
397 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp); | ||
398 | } | ||
399 | break; | ||
400 | case RemoteMsiSession::MsiDatabaseIsTablePersistent: | ||
401 | { | ||
402 | MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue; | ||
403 | const wchar_t* szTable = pReq->fields[1].szValue; | ||
404 | ret = ::MsiDatabaseIsTablePersistent(hDb, szTable); | ||
405 | } | ||
406 | break; | ||
407 | case RemoteMsiSession::MsiDoAction: | ||
408 | { | ||
409 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
410 | const wchar_t* szAction = pReq->fields[1].szValue; | ||
411 | ret = ::MsiDoAction(hInstall, szAction); | ||
412 | } | ||
413 | break; | ||
414 | case RemoteMsiSession::MsiEnumComponentCosts: | ||
415 | { | ||
416 | ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
417 | } | ||
418 | break; | ||
419 | case RemoteMsiSession::MsiEvaluateCondition: | ||
420 | { | ||
421 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
422 | const wchar_t* szCondition = pReq->fields[1].szValue; | ||
423 | ret = ::MsiEvaluateCondition(hInstall, szCondition); | ||
424 | } | ||
425 | break; | ||
426 | case RemoteMsiSession::MsiGetComponentState: | ||
427 | { | ||
428 | ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp); | ||
429 | } | ||
430 | break; | ||
431 | case RemoteMsiSession::MsiGetFeatureCost: | ||
432 | { | ||
433 | ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp); | ||
434 | } | ||
435 | break; | ||
436 | case RemoteMsiSession::MsiGetFeatureState: | ||
437 | { | ||
438 | ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp); | ||
439 | } | ||
440 | break; | ||
441 | case RemoteMsiSession::MsiGetFeatureValidStates: | ||
442 | { | ||
443 | ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp); | ||
444 | } | ||
445 | break; | ||
446 | case RemoteMsiSession::MsiGetLanguage: | ||
447 | { | ||
448 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
449 | ret = ::MsiGetLanguage(hInstall); | ||
450 | } | ||
451 | break; | ||
452 | case RemoteMsiSession::MsiGetLastErrorRecord: | ||
453 | { | ||
454 | ret = ::MsiGetLastErrorRecord(); | ||
455 | } | ||
456 | break; | ||
457 | case RemoteMsiSession::MsiGetMode: | ||
458 | { | ||
459 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
460 | MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue; | ||
461 | ret = ::MsiGetMode(hInstall, iRunMode); | ||
462 | } | ||
463 | break; | ||
464 | case RemoteMsiSession::MsiGetSourcePath: | ||
465 | { | ||
466 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
467 | } | ||
468 | break; | ||
469 | case RemoteMsiSession::MsiGetSummaryInformation: | ||
470 | { | ||
471 | ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp); | ||
472 | } | ||
473 | break; | ||
474 | case RemoteMsiSession::MsiGetTargetPath: | ||
475 | { | ||
476 | ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend); | ||
477 | } | ||
478 | break; | ||
479 | case RemoteMsiSession::MsiRecordDataSize: | ||
480 | { | ||
481 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
482 | UINT iField = pReq->fields[1].uiValue; | ||
483 | ret = ::MsiRecordDataSize(hRecord, iField); | ||
484 | } | ||
485 | break; | ||
486 | case RemoteMsiSession::MsiRecordReadStream: | ||
487 | { | ||
488 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
489 | UINT iField = pReq->fields[1].uiValue; | ||
490 | DWORD cbRead = (DWORD) pReq->fields[2].uiValue; | ||
491 | ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2); | ||
492 | if (ret == 0) | ||
493 | { | ||
494 | ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead); | ||
495 | if (ret == 0) | ||
496 | { | ||
497 | pResp->fields[1].vt = VT_STREAM; | ||
498 | pResp->fields[1].szValue = m_pBufSend; | ||
499 | pResp->fields[2].vt = VT_I4; | ||
500 | pResp->fields[2].uiValue = (UINT) cbRead; | ||
501 | } | ||
502 | } | ||
503 | } | ||
504 | break; | ||
505 | case RemoteMsiSession::MsiRecordSetStream: | ||
506 | { | ||
507 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
508 | UINT iField = pReq->fields[1].uiValue; | ||
509 | const wchar_t* szFilePath = pReq->fields[2].szValue; | ||
510 | ret = ::MsiRecordSetStream(hRecord, iField, szFilePath); | ||
511 | } | ||
512 | break; | ||
513 | case RemoteMsiSession::MsiSequence: | ||
514 | { | ||
515 | MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue; | ||
516 | const wchar_t* szTable = pReq->fields[1].szValue; | ||
517 | UINT iSequenceMode = pReq->fields[2].uiValue; | ||
518 | ret = ::MsiSequence(hRecord, szTable, iSequenceMode); | ||
519 | } | ||
520 | break; | ||
521 | case RemoteMsiSession::MsiSetComponentState: | ||
522 | { | ||
523 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
524 | const wchar_t* szComponent = pReq->fields[1].szValue; | ||
525 | INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; | ||
526 | ret = ::MsiSetComponentState(hInstall, szComponent, iState); | ||
527 | } | ||
528 | break; | ||
529 | case RemoteMsiSession::MsiSetFeatureAttributes: | ||
530 | { | ||
531 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
532 | const wchar_t* szFeature = pReq->fields[1].szValue; | ||
533 | DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue; | ||
534 | ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs); | ||
535 | } | ||
536 | break; | ||
537 | case RemoteMsiSession::MsiSetFeatureState: | ||
538 | { | ||
539 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
540 | const wchar_t* szFeature = pReq->fields[1].szValue; | ||
541 | INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue; | ||
542 | ret = ::MsiSetFeatureState(hInstall, szFeature, iState); | ||
543 | } | ||
544 | break; | ||
545 | case RemoteMsiSession::MsiSetInstallLevel: | ||
546 | { | ||
547 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
548 | int iInstallLevel = pReq->fields[1].iValue; | ||
549 | ret = ::MsiSetInstallLevel(hInstall, iInstallLevel); | ||
550 | } | ||
551 | break; | ||
552 | case RemoteMsiSession::MsiSetMode: | ||
553 | { | ||
554 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
555 | MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue; | ||
556 | BOOL fState = (BOOL) pReq->fields[2].iValue; | ||
557 | ret = ::MsiSetMode(hInstall, iRunMode, fState); | ||
558 | } | ||
559 | break; | ||
560 | case RemoteMsiSession::MsiSetTargetPath: | ||
561 | { | ||
562 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
563 | const wchar_t* szFolder = pReq->fields[1].szValue; | ||
564 | const wchar_t* szFolderPath = pReq->fields[2].szValue; | ||
565 | ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath); | ||
566 | } | ||
567 | break; | ||
568 | case RemoteMsiSession::MsiSummaryInfoGetProperty: | ||
569 | { | ||
570 | MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue; | ||
571 | UINT uiProperty = pReq->fields[1].uiValue; | ||
572 | UINT uiDataType; | ||
573 | int iValue; | ||
574 | FILETIME ftValue; | ||
575 | m_pBufSend[0] = L'\0'; | ||
576 | DWORD cchValue = m_cbBufSend; | ||
577 | ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); | ||
578 | if (ret == ERROR_MORE_DATA) | ||
579 | { | ||
580 | ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue); | ||
581 | if (ret == 0) | ||
582 | { | ||
583 | ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue); | ||
584 | } | ||
585 | } | ||
586 | if (ret == 0) | ||
587 | { | ||
588 | pResp->fields[1].vt = VT_UI4; | ||
589 | pResp->fields[1].uiValue = uiDataType; | ||
590 | |||
591 | switch (uiDataType) | ||
592 | { | ||
593 | case VT_I2: | ||
594 | case VT_I4: | ||
595 | pResp->fields[2].vt = VT_I4; | ||
596 | pResp->fields[2].iValue = iValue; | ||
597 | break; | ||
598 | case VT_FILETIME: | ||
599 | pResp->fields[2].vt = VT_UI4; | ||
600 | pResp->fields[2].iValue = ftValue.dwHighDateTime; | ||
601 | pResp->fields[3].vt = VT_UI4; | ||
602 | pResp->fields[3].iValue = ftValue.dwLowDateTime; | ||
603 | break; | ||
604 | case VT_LPSTR: | ||
605 | pResp->fields[2].vt = VT_LPWSTR; | ||
606 | pResp->fields[2].szValue = m_pBufSend; | ||
607 | break; | ||
608 | } | ||
609 | } | ||
610 | } | ||
611 | break; | ||
612 | case RemoteMsiSession::MsiVerifyDiskSpace: | ||
613 | { | ||
614 | MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue; | ||
615 | ret = ::MsiVerifyDiskSpace(hInstall); | ||
616 | } | ||
617 | break; | ||
618 | |||
619 | default: | ||
620 | { | ||
621 | ret = ERROR_INVALID_FUNCTION; | ||
622 | } | ||
623 | break; | ||
624 | } | ||
625 | } | ||
626 | |||
627 | pResp->fields[0].vt = VT_UI4; | ||
628 | pResp->fields[0].uiValue = ret; | ||
629 | } | ||
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 | // | ||
12 | class RemoteMsiSession | ||
13 | { | ||
14 | public: | ||
15 | |||
16 | // This enumeration MUST stay in sync with the | ||
17 | // managed equivalent in RemotableNativeMethods.cs! | ||
18 | enum RequestId | ||
19 | { | ||
20 | EndSession = 0, | ||
21 | MsiCloseHandle, | ||
22 | MsiCreateRecord, | ||
23 | MsiDatabaseGetPrimaryKeys, | ||
24 | MsiDatabaseIsTablePersistent, | ||
25 | MsiDatabaseOpenView, | ||
26 | MsiDoAction, | ||
27 | MsiEnumComponentCosts, | ||
28 | MsiEvaluateCondition, | ||
29 | MsiFormatRecord, | ||
30 | MsiGetActiveDatabase, | ||
31 | MsiGetComponentState, | ||
32 | MsiGetFeatureCost, | ||
33 | MsiGetFeatureState, | ||
34 | MsiGetFeatureValidStates, | ||
35 | MsiGetLanguage, | ||
36 | MsiGetLastErrorRecord, | ||
37 | MsiGetMode, | ||
38 | MsiGetProperty, | ||
39 | MsiGetSourcePath, | ||
40 | MsiGetSummaryInformation, | ||
41 | MsiGetTargetPath, | ||
42 | MsiProcessMessage, | ||
43 | MsiRecordClearData, | ||
44 | MsiRecordDataSize, | ||
45 | MsiRecordGetFieldCount, | ||
46 | MsiRecordGetInteger, | ||
47 | MsiRecordGetString, | ||
48 | MsiRecordIsNull, | ||
49 | MsiRecordReadStream, | ||
50 | MsiRecordSetInteger, | ||
51 | MsiRecordSetStream, | ||
52 | MsiRecordSetString, | ||
53 | MsiSequence, | ||
54 | MsiSetComponentState, | ||
55 | MsiSetFeatureAttributes, | ||
56 | MsiSetFeatureState, | ||
57 | MsiSetInstallLevel, | ||
58 | MsiSetMode, | ||
59 | MsiSetProperty, | ||
60 | MsiSetTargetPath, | ||
61 | MsiSummaryInfoGetProperty, | ||
62 | MsiVerifyDiskSpace, | ||
63 | MsiViewExecute, | ||
64 | MsiViewFetch, | ||
65 | MsiViewGetError, | ||
66 | MsiViewGetColumnInfo, | ||
67 | MsiViewModify, | ||
68 | }; | ||
69 | |||
70 | static const int MAX_REQUEST_FIELDS = 4; | ||
71 | |||
72 | // Used to pass data back and forth for remote API calls, | ||
73 | // including in & out params & return values. | ||
74 | // Only strings and ints are supported. | ||
75 | struct RequestData | ||
76 | { | ||
77 | struct | ||
78 | { | ||
79 | VARENUM vt; | ||
80 | union { | ||
81 | int iValue; | ||
82 | UINT uiValue; | ||
83 | DWORD cchValue; | ||
84 | LPWSTR szValue; | ||
85 | BYTE* sValue; | ||
86 | DWORD cbValue; | ||
87 | }; | ||
88 | } fields[MAX_REQUEST_FIELDS]; | ||
89 | }; | ||
90 | |||
91 | public: | ||
92 | |||
93 | // This value is set from the single data parameter in the EndSession request. | ||
94 | // It saves the exit code of the out-of-proc custom action. | ||
95 | int ExitCode; | ||
96 | |||
97 | ///////////////////////////////////////////////////////////////////////////////////// | ||
98 | // RemoteMsiSession constructor | ||
99 | // | ||
100 | // Creates a new remote session instance, for use either by the server | ||
101 | // or client process. | ||
102 | // | ||
103 | // szName - Identifies the session instance being remoted. The server and | ||
104 | // the client must use the same name. The name should be unique | ||
105 | // enough to avoid conflicting with other instances on the system. | ||
106 | // | ||
107 | // fServer - True if the calling process is the server process, false if the | ||
108 | // calling process is the client process. | ||
109 | // | ||
110 | RemoteMsiSession(const wchar_t* szName, bool fServer=true) | ||
111 | : m_fServer(fServer), | ||
112 | m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"), | ||
113 | m_szPipeName(NULL), | ||
114 | m_hPipe(NULL), | ||
115 | m_fConnecting(false), | ||
116 | m_fConnected(false), | ||
117 | m_hReceiveThread(NULL), | ||
118 | m_hReceiveStopEvent(NULL), | ||
119 | m_pBufReceive(NULL), | ||
120 | m_cbBufReceive(0), | ||
121 | m_pBufSend(NULL), | ||
122 | m_cbBufSend(0), | ||
123 | ExitCode(ERROR_INSTALL_FAILURE) | ||
124 | { | ||
125 | SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED)); | ||
126 | m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
127 | } | ||
128 | |||
129 | ///////////////////////////////////////////////////////////////////////////////////// | ||
130 | // RemoteMsiSession destructor | ||
131 | // | ||
132 | // Closes any open handles and frees any allocated memory. | ||
133 | // | ||
134 | ~RemoteMsiSession() | ||
135 | { | ||
136 | WaitExitCode(); | ||
137 | if (m_hPipe != NULL) | ||
138 | { | ||
139 | CloseHandle(m_hPipe); | ||
140 | m_hPipe = NULL; | ||
141 | } | ||
142 | if (m_overlapped.hEvent != NULL) | ||
143 | { | ||
144 | CloseHandle(m_overlapped.hEvent); | ||
145 | m_overlapped.hEvent = NULL; | ||
146 | } | ||
147 | if (m_szPipeName != NULL) | ||
148 | { | ||
149 | delete[] m_szPipeName; | ||
150 | m_szPipeName = NULL; | ||
151 | } | ||
152 | if (m_pBufReceive != NULL) | ||
153 | { | ||
154 | SecureZeroMemory(m_pBufReceive, m_cbBufReceive); | ||
155 | delete[] m_pBufReceive; | ||
156 | m_pBufReceive = NULL; | ||
157 | } | ||
158 | if (m_pBufSend != NULL) | ||
159 | { | ||
160 | SecureZeroMemory(m_pBufSend, m_cbBufSend); | ||
161 | delete[] m_pBufSend; | ||
162 | m_pBufSend = NULL; | ||
163 | } | ||
164 | m_fConnecting = false; | ||
165 | m_fConnected = false; | ||
166 | } | ||
167 | |||
168 | ///////////////////////////////////////////////////////////////////////////////////// | ||
169 | // RemoteMsiSession::WaitExitCode() | ||
170 | // | ||
171 | // Waits for the server processing thread to complete. | ||
172 | // | ||
173 | void WaitExitCode() | ||
174 | { | ||
175 | if (m_hReceiveThread != NULL) | ||
176 | { | ||
177 | SetEvent(m_hReceiveStopEvent); | ||
178 | WaitForSingleObject(m_hReceiveThread, INFINITE); | ||
179 | CloseHandle(m_hReceiveThread); | ||
180 | m_hReceiveThread = NULL; | ||
181 | } | ||
182 | } | ||
183 | |||
184 | ///////////////////////////////////////////////////////////////////////////////////// | ||
185 | // RemoteMsiSession::Connect() | ||
186 | // | ||
187 | // Connects the inter-process communication channel. | ||
188 | // (Currently implemented as a named pipe.) | ||
189 | // | ||
190 | // This method must be called first by the server process, then by the client | ||
191 | // process. The method does not block; the server will asynchronously wait | ||
192 | // for the client process to make the connection. | ||
193 | // | ||
194 | // Returns: 0 on success, Win32 error code on failure. | ||
195 | // | ||
196 | virtual DWORD Connect() | ||
197 | { | ||
198 | const wchar_t* szPipePrefix = L"\\\\.\\pipe\\"; | ||
199 | size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1; | ||
200 | m_szPipeName = new wchar_t[cchPipeNameBuf]; | ||
201 | |||
202 | if (m_szPipeName == NULL) | ||
203 | { | ||
204 | return ERROR_OUTOFMEMORY; | ||
205 | } | ||
206 | else | ||
207 | { | ||
208 | wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix); | ||
209 | wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName); | ||
210 | |||
211 | if (m_fServer) | ||
212 | { | ||
213 | return this->ConnectPipeServer(); | ||
214 | } | ||
215 | else | ||
216 | { | ||
217 | return this->ConnectPipeClient(); | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | |||
222 | ///////////////////////////////////////////////////////////////////////////////////// | ||
223 | // RemoteMsiSession::IsConnected() | ||
224 | // | ||
225 | // Checks if the server process and client process are currently connected. | ||
226 | // | ||
227 | virtual bool IsConnected() const | ||
228 | { | ||
229 | return m_fConnected; | ||
230 | } | ||
231 | |||
232 | ///////////////////////////////////////////////////////////////////////////////////// | ||
233 | // RemoteMsiSession::ProcessRequests() | ||
234 | // | ||
235 | // For use by the service process. Watches for requests in the input buffer and calls | ||
236 | // the callback for each one. | ||
237 | // | ||
238 | // This method does not block; it spawns a separate thread to do the work. | ||
239 | // | ||
240 | // Returns: 0 on success, Win32 error code on failure. | ||
241 | // | ||
242 | virtual DWORD ProcessRequests() | ||
243 | { | ||
244 | return this->StartProcessingReqests(); | ||
245 | } | ||
246 | |||
247 | ///////////////////////////////////////////////////////////////////////////////////// | ||
248 | // RemoteMsiSession::SendRequest() | ||
249 | // | ||
250 | // For use by the client process. Sends a request to the server and | ||
251 | // synchronously waits on a response, up to the timeout value. | ||
252 | // | ||
253 | // id - ID code of the MSI API call being requested. | ||
254 | // | ||
255 | // pRequest - Pointer to a data structure containing request parameters. | ||
256 | // | ||
257 | // ppResponse - [OUT] Pointer to a location that receives the response parameters. | ||
258 | // | ||
259 | // Returns: 0 on success, Win32 error code on failure. | ||
260 | // Returns WAIT_TIMEOUT if no response was received in time. | ||
261 | // | ||
262 | virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse) | ||
263 | { | ||
264 | if (m_fServer) | ||
265 | { | ||
266 | return ERROR_INVALID_OPERATION; | ||
267 | } | ||
268 | |||
269 | if (!m_fConnected) | ||
270 | { | ||
271 | *ppResponse = NULL; | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | DWORD dwRet = this->SendRequest(id, pRequest); | ||
276 | if (dwRet != 0) | ||
277 | { | ||
278 | return dwRet; | ||
279 | } | ||
280 | |||
281 | if (id != EndSession) | ||
282 | { | ||
283 | static RequestData response; | ||
284 | if (ppResponse != NULL) | ||
285 | { | ||
286 | *ppResponse = &response; | ||
287 | } | ||
288 | |||
289 | return this->ReceiveResponse(id, &response); | ||
290 | } | ||
291 | else | ||
292 | { | ||
293 | CloseHandle(m_hPipe); | ||
294 | m_hPipe = NULL; | ||
295 | m_fConnected = false; | ||
296 | return 0; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | private: | ||
301 | |||
302 | // | ||
303 | // Do not allow assignment. | ||
304 | // | ||
305 | RemoteMsiSession& operator=(const RemoteMsiSession&); | ||
306 | |||
307 | // | ||
308 | // Called only by the server process. | ||
309 | // Create a new thread to handle receiving requests. | ||
310 | // | ||
311 | DWORD StartProcessingReqests() | ||
312 | { | ||
313 | if (!m_fServer || m_hReceiveStopEvent != NULL) | ||
314 | { | ||
315 | return ERROR_INVALID_OPERATION; | ||
316 | } | ||
317 | |||
318 | DWORD dwRet = 0; | ||
319 | |||
320 | m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
321 | |||
322 | if (m_hReceiveStopEvent == NULL) | ||
323 | { | ||
324 | dwRet = GetLastError(); | ||
325 | } | ||
326 | else | ||
327 | { | ||
328 | if (m_hReceiveThread != NULL) | ||
329 | { | ||
330 | CloseHandle(m_hReceiveThread); | ||
331 | } | ||
332 | |||
333 | m_hReceiveThread = CreateThread(NULL, 0, | ||
334 | RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL); | ||
335 | |||
336 | if (m_hReceiveThread == NULL) | ||
337 | { | ||
338 | dwRet = GetLastError(); | ||
339 | CloseHandle(m_hReceiveStopEvent); | ||
340 | m_hReceiveStopEvent = NULL; | ||
341 | } | ||
342 | } | ||
343 | |||
344 | return dwRet; | ||
345 | } | ||
346 | |||
347 | // | ||
348 | // Called only by the watcher process. | ||
349 | // First verify the connection is complete. Then continually read and parse messages, | ||
350 | // invoke the callback, and send the replies. | ||
351 | // | ||
352 | static DWORD WINAPI ProcessRequestsThreadStatic(void* pv) | ||
353 | { | ||
354 | return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread(); | ||
355 | } | ||
356 | |||
357 | DWORD ProcessRequestsThread() | ||
358 | { | ||
359 | DWORD dwRet; | ||
360 | |||
361 | dwRet = CompleteConnection(); | ||
362 | if (dwRet != 0) | ||
363 | { | ||
364 | if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0; | ||
365 | } | ||
366 | |||
367 | while (m_fConnected) | ||
368 | { | ||
369 | RequestId id; | ||
370 | RequestData req; | ||
371 | dwRet = ReceiveRequest(&id, &req); | ||
372 | if (dwRet != 0) | ||
373 | { | ||
374 | if (dwRet == ERROR_OPERATION_ABORTED || | ||
375 | dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA) | ||
376 | { | ||
377 | dwRet = 0; | ||
378 | } | ||
379 | } | ||
380 | else | ||
381 | { | ||
382 | RequestData resp; | ||
383 | ProcessRequest(id, &req, &resp); | ||
384 | |||
385 | if (id == EndSession) | ||
386 | { | ||
387 | break; | ||
388 | } | ||
389 | |||
390 | dwRet = SendResponse(id, &resp); | ||
391 | if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA) | ||
392 | { | ||
393 | dwRet = 0; | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | |||
398 | CloseHandle(m_hReceiveStopEvent); | ||
399 | m_hReceiveStopEvent = NULL; | ||
400 | return dwRet; | ||
401 | } | ||
402 | |||
403 | // | ||
404 | // Called only by the server process's receive thread. | ||
405 | // Read one request into a RequestData object. | ||
406 | // | ||
407 | DWORD ReceiveRequest(RequestId* pId, RequestData* pReq) | ||
408 | { | ||
409 | DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId)); | ||
410 | |||
411 | if (dwRet == 0) | ||
412 | { | ||
413 | dwRet = this->ReadRequestData(pReq); | ||
414 | } | ||
415 | |||
416 | return dwRet; | ||
417 | } | ||
418 | |||
419 | // | ||
420 | // Called by the server process's receive thread or the client's request call | ||
421 | // to read the response. Read data from the pipe, allowing interruption by the | ||
422 | // stop event if on the server. | ||
423 | // | ||
424 | DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead) | ||
425 | { | ||
426 | DWORD dwRet = 0; | ||
427 | DWORD dwTotalBytesRead = 0; | ||
428 | |||
429 | while (dwRet == 0 && dwTotalBytesRead < cbRead) | ||
430 | { | ||
431 | DWORD dwBytesReadThisTime; | ||
432 | ResetEvent(m_overlapped.hEvent); | ||
433 | if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped)) | ||
434 | { | ||
435 | dwRet = GetLastError(); | ||
436 | if (dwRet == ERROR_IO_PENDING) | ||
437 | { | ||
438 | if (m_fServer) | ||
439 | { | ||
440 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
441 | dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
442 | } | ||
443 | else | ||
444 | { | ||
445 | dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); | ||
446 | } | ||
447 | |||
448 | if (dwRet == WAIT_OBJECT_0) | ||
449 | { | ||
450 | if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE)) | ||
451 | { | ||
452 | dwRet = GetLastError(); | ||
453 | } | ||
454 | } | ||
455 | else if (dwRet == WAIT_FAILED) | ||
456 | { | ||
457 | dwRet = GetLastError(); | ||
458 | } | ||
459 | else | ||
460 | { | ||
461 | dwRet = ERROR_OPERATION_ABORTED; | ||
462 | } | ||
463 | } | ||
464 | } | ||
465 | |||
466 | dwTotalBytesRead += dwBytesReadThisTime; | ||
467 | } | ||
468 | |||
469 | if (dwRet != 0) | ||
470 | { | ||
471 | if (m_fServer) | ||
472 | { | ||
473 | CancelIo(m_hPipe); | ||
474 | DisconnectNamedPipe(m_hPipe); | ||
475 | } | ||
476 | else | ||
477 | { | ||
478 | CloseHandle(m_hPipe); | ||
479 | m_hPipe = NULL; | ||
480 | } | ||
481 | m_fConnected = false; | ||
482 | } | ||
483 | |||
484 | return dwRet; | ||
485 | } | ||
486 | |||
487 | // | ||
488 | // Called only by the server process. | ||
489 | // Given a request, invoke the MSI API and return the response. | ||
490 | // This is implemented in RemoteMsi.cpp. | ||
491 | // | ||
492 | void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp); | ||
493 | |||
494 | // | ||
495 | // Called only by the client process. | ||
496 | // Send request data over the pipe. | ||
497 | // | ||
498 | DWORD SendRequest(RequestId id, const RequestData* pRequest) | ||
499 | { | ||
500 | DWORD dwRet = WriteRequestData(id, pRequest); | ||
501 | |||
502 | if (dwRet != 0) | ||
503 | { | ||
504 | m_fConnected = false; | ||
505 | CloseHandle(m_hPipe); | ||
506 | m_hPipe = NULL; | ||
507 | } | ||
508 | |||
509 | return dwRet; | ||
510 | } | ||
511 | |||
512 | // | ||
513 | // Called only by the server process. | ||
514 | // Just send a response over the pipe. | ||
515 | // | ||
516 | DWORD SendResponse(RequestId id, const RequestData* pResp) | ||
517 | { | ||
518 | DWORD dwRet = WriteRequestData(id, pResp); | ||
519 | |||
520 | if (dwRet != 0) | ||
521 | { | ||
522 | DisconnectNamedPipe(m_hPipe); | ||
523 | m_fConnected = false; | ||
524 | } | ||
525 | |||
526 | return dwRet; | ||
527 | } | ||
528 | |||
529 | // | ||
530 | // Called either by the client or server process. | ||
531 | // Writes data to the pipe for a request or response. | ||
532 | // | ||
533 | DWORD WriteRequestData(RequestId id, const RequestData* pReq) | ||
534 | { | ||
535 | DWORD dwRet = 0; | ||
536 | |||
537 | RequestData req = *pReq; // Make a copy because the const data can't be changed. | ||
538 | |||
539 | dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId)); | ||
540 | if (dwRet != 0) | ||
541 | { | ||
542 | return dwRet; | ||
543 | } | ||
544 | |||
545 | BYTE* sValues[MAX_REQUEST_FIELDS] = {0}; | ||
546 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
547 | { | ||
548 | if (req.fields[i].vt == VT_LPWSTR) | ||
549 | { | ||
550 | sValues[i] = (BYTE*) req.fields[i].szValue; | ||
551 | req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue); | ||
552 | } | ||
553 | else if (req.fields[i].vt == VT_STREAM) | ||
554 | { | ||
555 | sValues[i] = req.fields[i].sValue; | ||
556 | req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue; | ||
557 | } | ||
558 | } | ||
559 | |||
560 | dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData)); | ||
561 | if (dwRet != 0) | ||
562 | { | ||
563 | return dwRet; | ||
564 | } | ||
565 | |||
566 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
567 | { | ||
568 | if (sValues[i] != NULL) | ||
569 | { | ||
570 | DWORD cbValue; | ||
571 | if (req.fields[i].vt == VT_LPWSTR) | ||
572 | { | ||
573 | cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR); | ||
574 | } | ||
575 | else | ||
576 | { | ||
577 | cbValue = req.fields[i].cbValue; | ||
578 | } | ||
579 | |||
580 | dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue); | ||
581 | if (dwRet != 0) | ||
582 | { | ||
583 | break; | ||
584 | } | ||
585 | } | ||
586 | } | ||
587 | |||
588 | return dwRet; | ||
589 | } | ||
590 | |||
591 | // | ||
592 | // Called when writing a request or response. Writes data to | ||
593 | // the pipe, allowing interruption by the stop event if on the server. | ||
594 | // | ||
595 | DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite) | ||
596 | { | ||
597 | DWORD dwRet = 0; | ||
598 | DWORD dwTotalBytesWritten = 0; | ||
599 | |||
600 | while (dwRet == 0 && dwTotalBytesWritten < cbWrite) | ||
601 | { | ||
602 | DWORD dwBytesWrittenThisTime; | ||
603 | ResetEvent(m_overlapped.hEvent); | ||
604 | if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped)) | ||
605 | { | ||
606 | dwRet = GetLastError(); | ||
607 | if (dwRet == ERROR_IO_PENDING) | ||
608 | { | ||
609 | if (m_fServer) | ||
610 | { | ||
611 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
612 | dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
613 | } | ||
614 | else | ||
615 | { | ||
616 | dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE); | ||
617 | } | ||
618 | |||
619 | if (dwRet == WAIT_OBJECT_0) | ||
620 | { | ||
621 | if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE)) | ||
622 | { | ||
623 | dwRet = GetLastError(); | ||
624 | } | ||
625 | } | ||
626 | else if (dwRet == WAIT_FAILED) | ||
627 | { | ||
628 | dwRet = GetLastError(); | ||
629 | } | ||
630 | else | ||
631 | { | ||
632 | dwRet = ERROR_OPERATION_ABORTED; | ||
633 | } | ||
634 | } | ||
635 | } | ||
636 | |||
637 | dwTotalBytesWritten += dwBytesWrittenThisTime; | ||
638 | } | ||
639 | |||
640 | return dwRet; | ||
641 | } | ||
642 | |||
643 | // | ||
644 | // Called either by the client or server process. | ||
645 | // Reads data from the pipe for a request or response. | ||
646 | // | ||
647 | DWORD ReadRequestData(RequestData* pReq) | ||
648 | { | ||
649 | DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData)); | ||
650 | |||
651 | if (dwRet == 0) | ||
652 | { | ||
653 | DWORD cbData = 0; | ||
654 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
655 | { | ||
656 | if (pReq->fields[i].vt == VT_LPWSTR) | ||
657 | { | ||
658 | cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); | ||
659 | } | ||
660 | else if (pReq->fields[i].vt == VT_STREAM) | ||
661 | { | ||
662 | cbData += pReq->fields[i].cbValue; | ||
663 | } | ||
664 | } | ||
665 | |||
666 | if (cbData > 0) | ||
667 | { | ||
668 | if (!CheckRequestDataBuf(cbData)) | ||
669 | { | ||
670 | return ERROR_OUTOFMEMORY; | ||
671 | } | ||
672 | |||
673 | dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData); | ||
674 | if (dwRet == 0) | ||
675 | { | ||
676 | DWORD dwOffset = 0; | ||
677 | for (int i = 0; i < MAX_REQUEST_FIELDS; i++) | ||
678 | { | ||
679 | if (pReq->fields[i].vt == VT_LPWSTR) | ||
680 | { | ||
681 | LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset); | ||
682 | dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR); | ||
683 | pReq->fields[i].szValue = szTemp; | ||
684 | } | ||
685 | else if (pReq->fields[i].vt == VT_STREAM) | ||
686 | { | ||
687 | BYTE* sTemp = m_pBufReceive + dwOffset; | ||
688 | dwOffset += pReq->fields[i].cbValue; | ||
689 | pReq->fields[i].sValue = sTemp; | ||
690 | } | ||
691 | } | ||
692 | } | ||
693 | } | ||
694 | } | ||
695 | |||
696 | return dwRet; | ||
697 | } | ||
698 | |||
699 | // | ||
700 | // Called only by the client process. | ||
701 | // Wait for a response on the pipe. If no response is received before the timeout, | ||
702 | // then give up and close the connection. | ||
703 | // | ||
704 | DWORD ReceiveResponse(RequestId id, RequestData* pResp) | ||
705 | { | ||
706 | RequestId responseId; | ||
707 | DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId)); | ||
708 | if (dwRet == 0 && responseId != id) | ||
709 | { | ||
710 | dwRet = ERROR_OPERATION_ABORTED; | ||
711 | } | ||
712 | |||
713 | if (dwRet == 0) | ||
714 | { | ||
715 | dwRet = this->ReadRequestData(pResp); | ||
716 | } | ||
717 | |||
718 | return dwRet; | ||
719 | } | ||
720 | |||
721 | // | ||
722 | // Called only by the server process's receive thread. | ||
723 | // Try to complete and verify an asynchronous connection operation. | ||
724 | // | ||
725 | DWORD CompleteConnection() | ||
726 | { | ||
727 | DWORD dwRet = 0; | ||
728 | if (m_fConnecting) | ||
729 | { | ||
730 | HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent }; | ||
731 | DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE); | ||
732 | |||
733 | if (dwWaitRes == WAIT_OBJECT_0) | ||
734 | { | ||
735 | m_fConnecting = false; | ||
736 | |||
737 | DWORD dwUnused; | ||
738 | if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE)) | ||
739 | { | ||
740 | m_fConnected = true; | ||
741 | } | ||
742 | else | ||
743 | { | ||
744 | dwRet = GetLastError(); | ||
745 | } | ||
746 | } | ||
747 | else if (dwWaitRes == WAIT_FAILED) | ||
748 | { | ||
749 | CancelIo(m_hPipe); | ||
750 | dwRet = GetLastError(); | ||
751 | } | ||
752 | else | ||
753 | { | ||
754 | CancelIo(m_hPipe); | ||
755 | dwRet = ERROR_OPERATION_ABORTED; | ||
756 | } | ||
757 | } | ||
758 | return dwRet; | ||
759 | } | ||
760 | |||
761 | // | ||
762 | // Called only by the server process. | ||
763 | // Creates a named pipe instance and begins asynchronously waiting | ||
764 | // for a connection from the client process. | ||
765 | // | ||
766 | DWORD ConnectPipeServer() | ||
767 | { | ||
768 | DWORD dwRet = 0; | ||
769 | const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes | ||
770 | m_hPipe = CreateNamedPipe( | ||
771 | m_szPipeName, | ||
772 | PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, | ||
773 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, | ||
774 | 1, BUFSIZE, BUFSIZE, 0, NULL); | ||
775 | if (m_hPipe == INVALID_HANDLE_VALUE) | ||
776 | { | ||
777 | m_hPipe = NULL; | ||
778 | dwRet = GetLastError(); | ||
779 | } | ||
780 | else if (ConnectNamedPipe(m_hPipe, &m_overlapped)) | ||
781 | { | ||
782 | m_fConnected = true; | ||
783 | } | ||
784 | else | ||
785 | { | ||
786 | dwRet = GetLastError(); | ||
787 | |||
788 | if (dwRet == ERROR_PIPE_BUSY) | ||
789 | { | ||
790 | // All pipe instances are busy, so wait for a maximum of 20 seconds | ||
791 | dwRet = 0; | ||
792 | if (WaitNamedPipe(m_szPipeName, 20000)) | ||
793 | { | ||
794 | m_fConnected = true; | ||
795 | } | ||
796 | else | ||
797 | { | ||
798 | dwRet = GetLastError(); | ||
799 | } | ||
800 | } | ||
801 | |||
802 | if (dwRet == ERROR_IO_PENDING) | ||
803 | { | ||
804 | dwRet = 0; | ||
805 | m_fConnecting = true; | ||
806 | } | ||
807 | } | ||
808 | return dwRet; | ||
809 | } | ||
810 | |||
811 | // | ||
812 | // Called only by the client process. | ||
813 | // Attemps to open a connection to an existing named pipe instance | ||
814 | // which should have already been created by the server process. | ||
815 | // | ||
816 | DWORD ConnectPipeClient() | ||
817 | { | ||
818 | DWORD dwRet = 0; | ||
819 | m_hPipe = CreateFile( | ||
820 | m_szPipeName, GENERIC_READ | GENERIC_WRITE, | ||
821 | 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); | ||
822 | if (m_hPipe != INVALID_HANDLE_VALUE) | ||
823 | { | ||
824 | m_fConnected = true; | ||
825 | } | ||
826 | else | ||
827 | { | ||
828 | m_hPipe = NULL; | ||
829 | dwRet = GetLastError(); | ||
830 | } | ||
831 | return dwRet; | ||
832 | } | ||
833 | |||
834 | // | ||
835 | // Ensures that the request buffer is large enough to hold a request, | ||
836 | // reallocating the buffer if necessary. | ||
837 | // It will also reduce the buffer size if the previous allocation was very large. | ||
838 | // | ||
839 | BOOL CheckRequestDataBuf(DWORD cbBuf) | ||
840 | { | ||
841 | if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive)) | ||
842 | { | ||
843 | if (m_pBufReceive != NULL) | ||
844 | { | ||
845 | SecureZeroMemory(m_pBufReceive, m_cbBufReceive); | ||
846 | delete[] m_pBufReceive; | ||
847 | } | ||
848 | m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf); | ||
849 | m_pBufReceive = new BYTE[m_cbBufReceive]; | ||
850 | if (m_pBufReceive == NULL) | ||
851 | { | ||
852 | m_cbBufReceive = 0; | ||
853 | } | ||
854 | } | ||
855 | return m_pBufReceive != NULL; | ||
856 | } | ||
857 | |||
858 | private: | ||
859 | |||
860 | // Name of this instance. | ||
861 | const wchar_t* m_szName; | ||
862 | |||
863 | // "\\.\pipe\name" | ||
864 | wchar_t* m_szPipeName; | ||
865 | |||
866 | // Handle to the pipe instance. | ||
867 | HANDLE m_hPipe; | ||
868 | |||
869 | // Handle to the thread that receives requests. | ||
870 | HANDLE m_hReceiveThread; | ||
871 | |||
872 | // Handle to the event used to signal the receive thread to exit. | ||
873 | HANDLE m_hReceiveStopEvent; | ||
874 | |||
875 | // All pipe I/O is done in overlapped mode to avoid unintentional blocking. | ||
876 | OVERLAPPED m_overlapped; | ||
877 | |||
878 | // Dynamically-resized buffer for receiving requests. | ||
879 | BYTE* m_pBufReceive; | ||
880 | |||
881 | // Current size of the receive request buffer. | ||
882 | DWORD m_cbBufReceive; | ||
883 | |||
884 | // Dynamically-resized buffer for sending requests. | ||
885 | wchar_t* m_pBufSend; | ||
886 | |||
887 | // Current size of the send request buffer. | ||
888 | DWORD m_cbBufSend; | ||
889 | |||
890 | // True if this is the server process, false if this is the client process. | ||
891 | const bool m_fServer; | ||
892 | |||
893 | // True if an asynchronous connection operation is currently in progress. | ||
894 | bool m_fConnecting; | ||
895 | |||
896 | // True if the pipe is currently connected. | ||
897 | bool m_fConnected; | ||
898 | }; | ||
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 | |||
9 | HMODULE g_hModule; | ||
10 | bool g_fRunningOutOfProc = false; | ||
11 | |||
12 | RemoteMsiSession* g_pRemote = NULL; | ||
13 | |||
14 | // Prototypes for local functions. | ||
15 | // See the function definitions for comments. | ||
16 | |||
17 | bool InvokeManagedCustomAction(MSIHANDLE hSession, | ||
18 | _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult); | ||
19 | |||
20 | /// <summary> | ||
21 | /// Entry-point for the CA DLL when re-launched as a separate process; | ||
22 | /// connects the comm channel for remote MSI APIs, then invokes the | ||
23 | /// managed custom action entry-point. | ||
24 | /// </summary> | ||
25 | /// <remarks> | ||
26 | /// Do not change the parameters or calling-convention: RUNDLL32 | ||
27 | /// requires this exact signature. | ||
28 | /// </remarks> | ||
29 | extern "C" | ||
30 | void __stdcall InvokeManagedCustomActionOutOfProc( | ||
31 | __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow) | ||
32 | { | ||
33 | UNREFERENCED_PARAMETER(hwnd); | ||
34 | UNREFERENCED_PARAMETER(hinst); | ||
35 | UNREFERENCED_PARAMETER(nCmdShow); | ||
36 | |||
37 | g_fRunningOutOfProc = true; | ||
38 | |||
39 | const wchar_t* szSessionName = szCmdLine; | ||
40 | MSIHANDLE hSession; | ||
41 | const wchar_t* szEntryPoint; | ||
42 | |||
43 | int i; | ||
44 | for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
45 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
46 | hSession = _wtoi(szCmdLine + i); | ||
47 | |||
48 | for (; szCmdLine[i] && szCmdLine[i] != L' '; i++); | ||
49 | if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0'; | ||
50 | szEntryPoint = szCmdLine + i; | ||
51 | |||
52 | g_pRemote = new RemoteMsiSession(szSessionName, false); | ||
53 | g_pRemote->Connect(); | ||
54 | |||
55 | int ret = InvokeCustomAction(hSession, NULL, szEntryPoint); | ||
56 | |||
57 | RemoteMsiSession::RequestData requestData; | ||
58 | SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData)); | ||
59 | requestData.fields[0].vt = VT_I4; | ||
60 | requestData.fields[0].iValue = ret; | ||
61 | g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL); | ||
62 | delete g_pRemote; | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Re-launch this CA DLL as a separate process, and setup a comm channel | ||
67 | /// for remote MSI API calls back to this process. | ||
68 | /// </summary> | ||
69 | int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint) | ||
70 | { | ||
71 | wchar_t szSessionName[100] = {0}; | ||
72 | swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount()); | ||
73 | |||
74 | RemoteMsiSession remote(szSessionName, true); | ||
75 | |||
76 | DWORD ret = remote.Connect(); | ||
77 | if (ret != 0) | ||
78 | { | ||
79 | Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret); | ||
80 | return ERROR_INSTALL_FAILURE; | ||
81 | } | ||
82 | |||
83 | ret = remote.ProcessRequests(); | ||
84 | if (ret != 0) | ||
85 | { | ||
86 | Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret); | ||
87 | return ERROR_INSTALL_FAILURE; | ||
88 | } | ||
89 | |||
90 | wchar_t szModule[MAX_PATH] = {0}; | ||
91 | GetModuleFileName(g_hModule, szModule, MAX_PATH); | ||
92 | |||
93 | const wchar_t* rundll32 = L"rundll32.exe"; | ||
94 | wchar_t szRunDll32Path[MAX_PATH] = {0}; | ||
95 | GetSystemDirectory(szRunDll32Path, MAX_PATH); | ||
96 | wcscat_s(szRunDll32Path, MAX_PATH, L"\\"); | ||
97 | wcscat_s(szRunDll32Path, MAX_PATH, rundll32); | ||
98 | |||
99 | const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc"; | ||
100 | wchar_t szCommandLine[1024] = {0}; | ||
101 | swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s", | ||
102 | rundll32, szModule, entry, szSessionName, hSession, szEntryPoint); | ||
103 | |||
104 | STARTUPINFO si; | ||
105 | SecureZeroMemory(&si, sizeof(STARTUPINFO)); | ||
106 | si.cb = sizeof(STARTUPINFO); | ||
107 | |||
108 | PROCESS_INFORMATION pi; | ||
109 | SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); | ||
110 | |||
111 | if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE, | ||
112 | 0, NULL, NULL, &si, &pi)) | ||
113 | { | ||
114 | DWORD err = GetLastError(); | ||
115 | Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err); | ||
116 | return ERROR_INSTALL_FAILURE; | ||
117 | } | ||
118 | |||
119 | DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE); | ||
120 | if (dwWait != WAIT_OBJECT_0) | ||
121 | { | ||
122 | DWORD err = GetLastError(); | ||
123 | Log(hSession, L"Failed to wait for CA process. Error code: %d", err); | ||
124 | return ERROR_INSTALL_FAILURE; | ||
125 | } | ||
126 | |||
127 | DWORD dwExitCode; | ||
128 | BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode); | ||
129 | if (!bRet) | ||
130 | { | ||
131 | DWORD err = GetLastError(); | ||
132 | Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err); | ||
133 | return ERROR_INSTALL_FAILURE; | ||
134 | } | ||
135 | else if (dwExitCode != 0) | ||
136 | { | ||
137 | Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode); | ||
138 | return ERROR_INSTALL_FAILURE; | ||
139 | } | ||
140 | |||
141 | CloseHandle(pi.hThread); | ||
142 | CloseHandle(pi.hProcess); | ||
143 | |||
144 | remote.WaitExitCode(); | ||
145 | return remote.ExitCode; | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Entrypoint for the managed CA proxy (RemotableNativeMethods) to | ||
150 | /// call MSI APIs remotely. | ||
151 | /// </summary> | ||
152 | void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse) | ||
153 | { | ||
154 | if (g_fRunningOutOfProc) | ||
155 | { | ||
156 | g_pRemote->SendRequest(id, pRequest, ppResponse); | ||
157 | } | ||
158 | else | ||
159 | { | ||
160 | *ppResponse = NULL; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | /// <summary> | ||
165 | /// Invokes a managed custom action from native code by | ||
166 | /// extracting the package to a temporary working directory | ||
167 | /// then hosting the CLR and locating and calling the entrypoint. | ||
168 | /// </summary> | ||
169 | /// <param name="hSession">Handle to the installation session. | ||
170 | /// Passed to custom action entrypoints by the installer engine.</param> | ||
171 | /// <param name="szWorkingDir">Directory containing the CA binaries | ||
172 | /// and the CustomAction.config file defining the entrypoints. | ||
173 | /// This may be NULL, in which case the current module must have | ||
174 | /// a concatenated cabinet containing those files, which will be | ||
175 | /// extracted to a temporary directory.</param> | ||
176 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
177 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
178 | /// string, or a simple name that maps to a full entrypoint definition | ||
179 | /// in CustomAction.config.</param> | ||
180 | /// <returns>The value returned by the managed custom action method, | ||
181 | /// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns> | ||
182 | int InvokeCustomAction(MSIHANDLE hSession, | ||
183 | const wchar_t* szWorkingDir, const wchar_t* szEntryPoint) | ||
184 | { | ||
185 | #ifdef MANAGED_CAs_OUT_OF_PROC | ||
186 | if (!g_fRunningOutOfProc && szWorkingDir == NULL) | ||
187 | { | ||
188 | return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint); | ||
189 | } | ||
190 | #endif | ||
191 | |||
192 | wchar_t szTempDir[MAX_PATH]; | ||
193 | bool fDeleteTemp = false; | ||
194 | if (szWorkingDir == NULL) | ||
195 | { | ||
196 | if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH)) | ||
197 | { | ||
198 | return ERROR_INSTALL_FAILURE; | ||
199 | } | ||
200 | szWorkingDir = szTempDir; | ||
201 | fDeleteTemp = true; | ||
202 | } | ||
203 | |||
204 | wchar_t szConfigFilePath[MAX_PATH + 20]; | ||
205 | StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir); | ||
206 | StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config"); | ||
207 | |||
208 | const wchar_t* szConfigFile = szConfigFilePath; | ||
209 | if (!::PathFileExists(szConfigFilePath)) | ||
210 | { | ||
211 | szConfigFile = NULL; | ||
212 | } | ||
213 | |||
214 | wchar_t szWIAssembly[MAX_PATH + 50]; | ||
215 | StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir); | ||
216 | StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll"); | ||
217 | |||
218 | int iResult = ERROR_INSTALL_FAILURE; | ||
219 | ICorRuntimeHost* pHost; | ||
220 | if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost)) | ||
221 | { | ||
222 | _AppDomain* pAppDomain; | ||
223 | if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir, | ||
224 | szConfigFile, &pAppDomain)) | ||
225 | { | ||
226 | if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult)) | ||
227 | { | ||
228 | iResult = ERROR_INSTALL_FAILURE; | ||
229 | } | ||
230 | HRESULT hr = pHost->UnloadDomain(pAppDomain); | ||
231 | if (FAILED(hr)) | ||
232 | { | ||
233 | Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr); | ||
234 | } | ||
235 | pAppDomain->Release(); | ||
236 | } | ||
237 | |||
238 | pHost->Stop(); | ||
239 | pHost->Release(); | ||
240 | } | ||
241 | |||
242 | if (fDeleteTemp) | ||
243 | { | ||
244 | DeleteDirectory(szTempDir); | ||
245 | } | ||
246 | return iResult; | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Called by the system when the DLL is loaded. | ||
251 | /// Saves the module handle for later use. | ||
252 | /// </summary> | ||
253 | BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved) | ||
254 | { | ||
255 | UNREFERENCED_PARAMETER(pReserved); | ||
256 | |||
257 | switch (dwReason) | ||
258 | { | ||
259 | case DLL_PROCESS_ATTACH: | ||
260 | g_hModule = hModule; | ||
261 | break; | ||
262 | case DLL_THREAD_ATTACH: | ||
263 | case DLL_THREAD_DETACH: | ||
264 | case DLL_PROCESS_DETACH: | ||
265 | break; | ||
266 | } | ||
267 | return TRUE; | ||
268 | } | ||
269 | |||
270 | /// <summary> | ||
271 | /// Loads and invokes the managed portion of the proxy. | ||
272 | /// </summary> | ||
273 | /// <param name="hSession">Handle to the installer session, | ||
274 | /// used for logging errors and to be passed on to the custom action.</param> | ||
275 | /// <param name="pAppDomain">AppDomain which has its application | ||
276 | /// base set to the CA working directory.</param> | ||
277 | /// <param name="szEntryPoint">Name of the CA entrypoint to be invoked. | ||
278 | /// This must be either an explicit "AssemblyName!Namespace.Class.Method" | ||
279 | /// string, or a simple name that maps to a full entrypoint definition | ||
280 | /// in CustomAction.config.</param> | ||
281 | /// <param name="piResult">Return value of the invoked custom | ||
282 | /// action method.</param> | ||
283 | /// <returns>True if the managed proxy was invoked successfully, | ||
284 | /// false if there was some error. Note the custom action itself may | ||
285 | /// return an error via piResult while this method still returns true | ||
286 | /// since the invocation was successful.</returns> | ||
287 | bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
288 | const wchar_t* szEntryPoint, int* piResult) | ||
289 | { | ||
290 | VARIANT vResult; | ||
291 | ::VariantInit(&vResult); | ||
292 | |||
293 | const bool f64bit = (sizeof(void*) == sizeof(LONGLONG)); | ||
294 | const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller"; | ||
295 | const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy"; | ||
296 | const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32"); | ||
297 | |||
298 | _MethodInfo* pCAInvokeMethod; | ||
299 | if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName, | ||
300 | szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod)) | ||
301 | { | ||
302 | return false; | ||
303 | } | ||
304 | |||
305 | HRESULT hr; | ||
306 | VARIANT vNull; | ||
307 | vNull.vt = VT_EMPTY; | ||
308 | SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3); | ||
309 | VARIANT vSessionHandle; | ||
310 | vSessionHandle.vt = VT_I4; | ||
311 | vSessionHandle.intVal = hSession; | ||
312 | LONG index = 0; | ||
313 | hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle); | ||
314 | if (FAILED(hr)) goto LExit; | ||
315 | VARIANT vEntryPoint; | ||
316 | vEntryPoint.vt = VT_BSTR; | ||
317 | vEntryPoint.bstrVal = SysAllocString(szEntryPoint); | ||
318 | if (vEntryPoint.bstrVal == NULL) | ||
319 | { | ||
320 | hr = E_OUTOFMEMORY; | ||
321 | goto LExit; | ||
322 | } | ||
323 | index = 1; | ||
324 | hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint); | ||
325 | if (FAILED(hr)) goto LExit; | ||
326 | VARIANT vRemotingFunctionPtr; | ||
327 | #pragma warning(push) | ||
328 | #pragma warning(disable:4127) // conditional expression is constant | ||
329 | if (f64bit) | ||
330 | #pragma warning(pop) | ||
331 | { | ||
332 | vRemotingFunctionPtr.vt = VT_I8; | ||
333 | vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
334 | } | ||
335 | else | ||
336 | { | ||
337 | vRemotingFunctionPtr.vt = VT_I4; | ||
338 | #pragma warning(push) | ||
339 | #pragma warning(disable:4302) // truncation | ||
340 | #pragma warning(disable:4311) // pointer truncation | ||
341 | vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL); | ||
342 | #pragma warning(pop) | ||
343 | } | ||
344 | index = 2; | ||
345 | hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr); | ||
346 | if (FAILED(hr)) goto LExit; | ||
347 | |||
348 | hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult); | ||
349 | |||
350 | LExit: | ||
351 | SafeArrayDestroy(saArgs); | ||
352 | pCAInvokeMethod->Release(); | ||
353 | |||
354 | if (FAILED(hr)) | ||
355 | { | ||
356 | Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr); | ||
357 | return false; | ||
358 | } | ||
359 | |||
360 | *piResult = vResult.intVal; | ||
361 | return true; | ||
362 | } | ||
363 | |||
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> | ||
10 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...) | ||
11 | { | ||
12 | const int LOG_BUFSIZE = 4096; | ||
13 | wchar_t szBuf[LOG_BUFSIZE]; | ||
14 | va_list args; | ||
15 | va_start(args, szMessage); | ||
16 | StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args); | ||
17 | |||
18 | if (!g_fRunningOutOfProc || NULL == g_pRemote) | ||
19 | { | ||
20 | MSIHANDLE hRec = MsiCreateRecord(1); | ||
21 | MsiRecordSetString(hRec, 0, L"SFXCA: [1]"); | ||
22 | MsiRecordSetString(hRec, 1, szBuf); | ||
23 | MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec); | ||
24 | MsiCloseHandle(hRec); | ||
25 | } | ||
26 | else | ||
27 | { | ||
28 | // Logging is the only remote-MSI operation done from unmanaged code. | ||
29 | // It's not very convenient here because part of the infrastructure | ||
30 | // for remote MSI APIs is on the managed side. | ||
31 | |||
32 | RemoteMsiSession::RequestData req; | ||
33 | RemoteMsiSession::RequestData* pResp = NULL; | ||
34 | SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData)); | ||
35 | |||
36 | req.fields[0].vt = VT_UI4; | ||
37 | req.fields[0].uiValue = 1; | ||
38 | g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp); | ||
39 | MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue; | ||
40 | |||
41 | req.fields[0].vt = VT_I4; | ||
42 | req.fields[0].iValue = (int) hRec; | ||
43 | req.fields[1].vt = VT_UI4; | ||
44 | req.fields[1].uiValue = 0; | ||
45 | req.fields[2].vt = VT_LPWSTR; | ||
46 | req.fields[2].szValue = L"SFXCA: [1]"; | ||
47 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); | ||
48 | |||
49 | req.fields[0].vt = VT_I4; | ||
50 | req.fields[0].iValue = (int) hRec; | ||
51 | req.fields[1].vt = VT_UI4; | ||
52 | req.fields[1].uiValue = 1; | ||
53 | req.fields[2].vt = VT_LPWSTR; | ||
54 | req.fields[2].szValue = szBuf; | ||
55 | g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp); | ||
56 | |||
57 | req.fields[0].vt = VT_I4; | ||
58 | req.fields[0].iValue = (int) hSession; | ||
59 | req.fields[1].vt = VT_I4; | ||
60 | req.fields[1].iValue = (int) INSTALLMESSAGE_INFO; | ||
61 | req.fields[2].vt = VT_I4; | ||
62 | req.fields[2].iValue = (int) hRec; | ||
63 | g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp); | ||
64 | |||
65 | req.fields[0].vt = VT_I4; | ||
66 | req.fields[0].iValue = (int) hRec; | ||
67 | req.fields[1].vt = VT_EMPTY; | ||
68 | req.fields[2].vt = VT_EMPTY; | ||
69 | g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | /// <summary> | ||
74 | /// Deletes a directory, including all files and subdirectories. | ||
75 | /// </summary> | ||
76 | /// <param name="szDir">Path to the directory to delete, | ||
77 | /// not including a trailing backslash.</param> | ||
78 | /// <returns>True if the directory was successfully deleted, or false | ||
79 | /// if the deletion failed (most likely because some files were locked). | ||
80 | /// </returns> | ||
81 | bool DeleteDirectory(const wchar_t* szDir) | ||
82 | { | ||
83 | size_t cchDir = wcslen(szDir); | ||
84 | size_t cchPathBuf = cchDir + 3 + MAX_PATH; | ||
85 | wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t)); | ||
86 | if (szPath == NULL) return false; | ||
87 | StringCchCopy(szPath, cchPathBuf, szDir); | ||
88 | StringCchCat(szPath, cchPathBuf, L"\\*"); | ||
89 | WIN32_FIND_DATA fd; | ||
90 | HANDLE hSearch = FindFirstFile(szPath, &fd); | ||
91 | while (hSearch != INVALID_HANDLE_VALUE) | ||
92 | { | ||
93 | StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); | ||
94 | if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) | ||
95 | { | ||
96 | if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) | ||
97 | { | ||
98 | DeleteDirectory(szPath); | ||
99 | } | ||
100 | } | ||
101 | else | ||
102 | { | ||
103 | DeleteFile(szPath); | ||
104 | } | ||
105 | if (!FindNextFile(hSearch, &fd)) | ||
106 | { | ||
107 | FindClose(hSearch); | ||
108 | hSearch = INVALID_HANDLE_VALUE; | ||
109 | } | ||
110 | } | ||
111 | return RemoveDirectory(szDir) != 0; | ||
112 | } | ||
113 | |||
114 | bool DirectoryExists(const wchar_t* szDir) | ||
115 | { | ||
116 | if (szDir != NULL) | ||
117 | { | ||
118 | DWORD dwAttrs = GetFileAttributes(szDir); | ||
119 | if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0) | ||
120 | { | ||
121 | return true; | ||
122 | } | ||
123 | } | ||
124 | return false; | ||
125 | } | ||
126 | |||
127 | /// <summary> | ||
128 | /// Extracts a cabinet that is concatenated to a module | ||
129 | /// to a new temporary directory. | ||
130 | /// </summary> | ||
131 | /// <param name="hSession">Handle to the installer session, | ||
132 | /// used just for logging.</param> | ||
133 | /// <param name="hModule">Module that has the concatenated cabinet.</param> | ||
134 | /// <param name="szTempDir">Buffer for returning the path of the | ||
135 | /// created temp directory.</param> | ||
136 | /// <param name="cchTempDirBuf">Size in characters of the buffer. | ||
137 | /// <returns>True if the files were extracted, or false if the | ||
138 | /// buffer was too small or the directory could not be created | ||
139 | /// or the extraction failed for some other reason.</returns> | ||
140 | __success(return != false) | ||
141 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, | ||
142 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf) | ||
143 | { | ||
144 | wchar_t szModule[MAX_PATH]; | ||
145 | DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1); | ||
146 | if (cchCopied == 0) | ||
147 | { | ||
148 | Log(hSession, L"Failed to get module path. Error code %d.", GetLastError()); | ||
149 | return false; | ||
150 | } | ||
151 | else if (cchCopied == MAX_PATH - 1) | ||
152 | { | ||
153 | Log(hSession, L"Failed to get module path -- path is too long."); | ||
154 | return false; | ||
155 | } | ||
156 | |||
157 | if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1) | ||
158 | { | ||
159 | Log(hSession, L"Temp directory buffer is NULL or too small."); | ||
160 | return false; | ||
161 | } | ||
162 | StringCchCopy(szTempDir, cchTempDirBuf, szModule); | ||
163 | StringCchCat(szTempDir, cchTempDirBuf, L"-"); | ||
164 | |||
165 | DWORD cchTempDir = (DWORD) wcslen(szTempDir); | ||
166 | for (int i = 0; DirectoryExists(szTempDir); i++) | ||
167 | { | ||
168 | swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); | ||
169 | } | ||
170 | |||
171 | if (!CreateDirectory(szTempDir, NULL)) | ||
172 | { | ||
173 | cchCopied = GetTempPath(cchTempDirBuf, szTempDir); | ||
174 | if (cchCopied == 0 || cchCopied >= cchTempDirBuf) | ||
175 | { | ||
176 | Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError()); | ||
177 | return false; | ||
178 | } | ||
179 | |||
180 | wchar_t* szModuleName = wcsrchr(szModule, L'\\'); | ||
181 | if (szModuleName == NULL) szModuleName = szModule; | ||
182 | else szModuleName = szModuleName + 1; | ||
183 | StringCchCat(szTempDir, cchTempDirBuf, szModuleName); | ||
184 | StringCchCat(szTempDir, cchTempDirBuf, L"-"); | ||
185 | |||
186 | cchTempDir = (DWORD) wcslen(szTempDir); | ||
187 | for (int i = 0; DirectoryExists(szTempDir); i++) | ||
188 | { | ||
189 | swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i); | ||
190 | } | ||
191 | |||
192 | if (!CreateDirectory(szTempDir, NULL)) | ||
193 | { | ||
194 | Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError()); | ||
195 | return false; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir); | ||
200 | int err = ExtractCabinet(szModule, szTempDir); | ||
201 | if (err != 0) | ||
202 | { | ||
203 | Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err); | ||
204 | DeleteDirectory(szTempDir); | ||
205 | return false; | ||
206 | } | ||
207 | return true; | ||
208 | } | ||
209 | |||
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 | |||
5 | void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...); | ||
6 | |||
7 | int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir); | ||
8 | |||
9 | bool DeleteDirectory(const wchar_t* szDir); | ||
10 | |||
11 | __success(return != false) | ||
12 | bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule, | ||
13 | __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf); | ||
14 | |||
15 | bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile, | ||
16 | const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost); | ||
17 | |||
18 | bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost, | ||
19 | const wchar_t* szName, const wchar_t* szAppBase, | ||
20 | const wchar_t* szConfigFile, _AppDomain** ppAppDomain); | ||
21 | |||
22 | bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain, | ||
23 | const wchar_t* szAssembly, const wchar_t* szClass, | ||
24 | const wchar_t* szMethod, _MethodInfo** ppCAMethod); | ||
25 | |||
26 | extern HMODULE g_hModule; | ||
27 | extern bool g_fRunningOutOfProc; | ||
28 | |||
29 | extern RemoteMsiSession* g_pRemote; | ||
30 | |||
31 | |||
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") | ||
18 | using 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 | |||
3 | using System; | ||
4 | using System.IO; | ||
5 | using System.Reflection; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Globalization; | ||
10 | using System.Text.RegularExpressions; | ||
11 | using WixToolset.Dtf.WindowsInstaller; | ||
12 | using WixToolset.Dtf.WindowsInstaller.Package; | ||
13 | |||
14 | [assembly: AssemblyDescription("Windows Installer package file extraction and update tool")] | ||
15 | |||
16 | |||
17 | /// <summary> | ||
18 | /// Shows sample use of the InstallPackage class. | ||
19 | /// </summary> | ||
20 | public class WiFile | ||
21 | { | ||
22 | public static void Usage(TextWriter w) | ||
23 | { | ||
24 | w.WriteLine("Usage: WiFile.exe package.msi /l [filename,filename2,...]"); | ||
25 | w.WriteLine("Usage: WiFile.exe package.msi /x [filename,filename2,...]"); | ||
26 | w.WriteLine("Usage: WiFile.exe package.msi /u [filename,filename2,...]"); | ||
27 | w.WriteLine(); | ||
28 | w.WriteLine("Lists (/l), extracts (/x) or updates (/u) files in an MSI or MSM."); | ||
29 | w.WriteLine("Files are extracted using their source path relative to the package."); | ||
30 | w.WriteLine("Specified filenames do not include paths."); | ||
31 | w.WriteLine("Filenames may be a pattern such as *.exe or file?.dll"); | ||
32 | } | ||
33 | |||
34 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] | ||
35 | public static int Main(string[] args) | ||
36 | { | ||
37 | if(!(args.Length == 2 || args.Length == 3)) | ||
38 | { | ||
39 | Usage(Console.Out); | ||
40 | return -1; | ||
41 | } | ||
42 | |||
43 | string msiFile = args[0]; | ||
44 | |||
45 | string option = args[1].ToLowerInvariant(); | ||
46 | if(option.StartsWith("-", StringComparison.Ordinal)) option = "/" + option.Substring(1); | ||
47 | |||
48 | string[] fileNames = null; | ||
49 | if(args.Length == 3) | ||
50 | { | ||
51 | fileNames = args[2].Split(','); | ||
52 | } | ||
53 | |||
54 | try | ||
55 | { | ||
56 | switch(option) | ||
57 | { | ||
58 | case "/l": | ||
59 | using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly)) | ||
60 | { | ||
61 | pkg.Message += new InstallPackageMessageHandler(Console.WriteLine); | ||
62 | IEnumerable<string> fileKeys = (fileNames != null ? FindFileKeys(pkg, fileNames) : pkg.Files.Keys); | ||
63 | |||
64 | foreach(string fileKey in fileKeys) | ||
65 | { | ||
66 | Console.WriteLine(pkg.Files[fileKey]); | ||
67 | } | ||
68 | } | ||
69 | break; | ||
70 | |||
71 | case "/x": | ||
72 | using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly)) | ||
73 | { | ||
74 | pkg.Message += new InstallPackageMessageHandler(Console.WriteLine); | ||
75 | ICollection<string> fileKeys = FindFileKeys(pkg, fileNames); | ||
76 | |||
77 | pkg.ExtractFiles(fileKeys); | ||
78 | } | ||
79 | break; | ||
80 | |||
81 | case "/u": | ||
82 | using(InstallPackage pkg = new InstallPackage(msiFile, DatabaseOpenMode.Transact)) | ||
83 | { | ||
84 | pkg.Message += new InstallPackageMessageHandler(Console.WriteLine); | ||
85 | ICollection<string> fileKeys = FindFileKeys(pkg, fileNames); | ||
86 | |||
87 | pkg.UpdateFiles(fileKeys); | ||
88 | pkg.Commit(); | ||
89 | } | ||
90 | break; | ||
91 | |||
92 | default: | ||
93 | Usage(Console.Out); | ||
94 | return -1; | ||
95 | } | ||
96 | } | ||
97 | catch(InstallerException iex) | ||
98 | { | ||
99 | Console.WriteLine("Error: " + iex.Message); | ||
100 | return iex.ErrorCode != 0 ? iex.ErrorCode : 1; | ||
101 | } | ||
102 | catch(FileNotFoundException fnfex) | ||
103 | { | ||
104 | Console.WriteLine(fnfex.Message); | ||
105 | return 2; | ||
106 | } | ||
107 | catch(Exception ex) | ||
108 | { | ||
109 | Console.WriteLine("Error: " + ex.Message); | ||
110 | return 1; | ||
111 | } | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static ICollection<string> FindFileKeys(InstallPackage pkg, ICollection<string> fileNames) | ||
116 | { | ||
117 | List<string> fileKeys = null; | ||
118 | if(fileNames != null) | ||
119 | { | ||
120 | fileKeys = new List<string>(); | ||
121 | foreach(string fileName in fileNames) | ||
122 | { | ||
123 | string[] foundFileKeys = null; | ||
124 | if(fileName.IndexOfAny(new char[] { '*', '?' }) >= 0) | ||
125 | { | ||
126 | foundFileKeys = pkg.FindFiles(FilePatternToRegex(fileName)); | ||
127 | } | ||
128 | else | ||
129 | { | ||
130 | foundFileKeys = pkg.FindFiles(fileName); | ||
131 | } | ||
132 | fileKeys.AddRange(foundFileKeys); | ||
133 | } | ||
134 | if(fileKeys.Count == 0) | ||
135 | { | ||
136 | throw new FileNotFoundException("Files not found in package."); | ||
137 | } | ||
138 | } | ||
139 | return fileKeys; | ||
140 | } | ||
141 | |||
142 | static Regex FilePatternToRegex(string pattern) | ||
143 | { | ||
144 | return new Regex("^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$", | ||
145 | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); | ||
146 | } | ||
147 | } | ||
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 | |||
3 | using 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 | |||
3 | namespace WixToolset.Dtf.Samples.XPack | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Text; | ||
9 | using WixToolset.Dtf.Compression; | ||
10 | |||
11 | public class XPack | ||
12 | { | ||
13 | public static void Usage(TextWriter writer) | ||
14 | { | ||
15 | writer.WriteLine("Usage: XPack /P <archive.cab> <directory>"); | ||
16 | writer.WriteLine("Usage: XPack /P <archive.zip> <directory>"); | ||
17 | writer.WriteLine(); | ||
18 | writer.WriteLine("Packs all files in a directory tree into an archive,"); | ||
19 | writer.WriteLine("using either the cab or zip format. Any existing archive"); | ||
20 | writer.WriteLine("with the same name will be overwritten."); | ||
21 | writer.WriteLine(); | ||
22 | writer.WriteLine("Usage: XPack /U <archive.cab> <directory>"); | ||
23 | writer.WriteLine("Usage: XPack /U <archive.zip> <directory>"); | ||
24 | writer.WriteLine(); | ||
25 | writer.WriteLine("Unpacks all files from a cab or zip archive to the"); | ||
26 | writer.WriteLine("specified directory. Any existing files with the same"); | ||
27 | writer.WriteLine("names will be overwritten."); | ||
28 | } | ||
29 | |||
30 | public static void Main(string[] args) | ||
31 | { | ||
32 | try | ||
33 | { | ||
34 | if (args.Length == 3 && args[0].ToUpperInvariant() == "/P") | ||
35 | { | ||
36 | ArchiveInfo a = GetArchive(args[1]); | ||
37 | a.Pack(args[2], true, CompressionLevel.Max, ProgressHandler); | ||
38 | } | ||
39 | else if (args.Length == 3 && args[0].ToUpperInvariant() == "/U") | ||
40 | { | ||
41 | ArchiveInfo a = GetArchive(args[1]); | ||
42 | a.Unpack(args[2], ProgressHandler); | ||
43 | } | ||
44 | else | ||
45 | { | ||
46 | Usage(Console.Out); | ||
47 | } | ||
48 | } | ||
49 | catch (Exception ex) | ||
50 | { | ||
51 | Console.WriteLine(ex); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | private static void ProgressHandler(object source, ArchiveProgressEventArgs e) | ||
56 | { | ||
57 | if (e.ProgressType == ArchiveProgressType.StartFile) | ||
58 | { | ||
59 | Console.WriteLine(e.CurrentFileName); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | private static ArchiveInfo GetArchive(string name) | ||
64 | { | ||
65 | string extension = Path.GetExtension(name).ToUpperInvariant(); | ||
66 | if (extension == ".CAB") | ||
67 | { | ||
68 | return new WixToolset.Dtf.Compression.Cab.CabInfo(name); | ||
69 | } | ||
70 | else if (extension == ".ZIP") | ||
71 | { | ||
72 | return new WixToolset.Dtf.Compression.Zip.ZipInfo(name); | ||
73 | } | ||
74 | else | ||
75 | { | ||
76 | throw new ArgumentException("Unknown archive file extension: " + extension); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | } | ||
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> | ||