For some of our projects we're looking to not only deploy assemblies to the GAC, but generate native images and copy the debug symbols to the GAC (which buys us line numbers in exceptions -- it's much easier to debug when you know the line number!). I've been experimenting with the VS2005 Setup Project to try and automate the process. During this experimentation realized a number of short comings:
It seems as usual the Setup Project that ships with VS2005 is weak in a number of areas (e.g. deployment of multiple instance applications). If the product groups were all forced to use the Setup Project to deploy all Microsoft apps I bet it would change drastically (hint hint MSFT). At this point I don't want to pay a billion dollars for InstallShield (which in past experience tends to crash numerous times), nor do I want to have to script out something for a seamingly common set of functions that I'm sure a lot of us developers have needs for.
For anyone who is trying to NGen or copy the Debug Symbols (aka Program Database, PDB) to the GAC during installation below is a snippet of my Installer class. A few notes:
1 private string AssemblyCachePath, FrameworkPath;
2 private string[] AssemblyNames = new string[]
3 { "MyAssembly", "MySecondAssembly" };
4
5 public Installer()
6 {
7 FrameworkPath = RuntimeEnvironment.GetRuntimeDirectory();
8
9 int MaxPathLength = 1024;
10 StringBuilder Buffer = new StringBuilder(MaxPathLength);
11 GetCachePath(ASM_CACHE_FLAGS.ASM_CACHE_ROOT, Buffer, ref MaxPathLength);
12 AssemblyCachePath = Buffer.ToString();
13 //...
14 }
15
16 [DllImport("fusion.dll")]
17 public extern static int GetCachePath(
18 ASM_CACHE_FLAGS dwCacheFlags,
19 [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwzCachePath,
20 ref int pcchPath);
21
22 [SecurityPermission(SecurityAction.Demand)]
23 public override void Install(System.Collections.IDictionary stateSaver)
24 {
25 base.Install(stateSaver);
26
27 Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
28 AssemblyName ExecutingAssemblyName = ExecutingAssembly.GetName();
29 string ExecutingAssemblyFullName = ExecutingAssemblyName.FullName;
30
31 ProcessStartInfo NGenStartInfo = new ProcessStartInfo(
32 Path.Combine( FrameworkPath, "ngen.exe" )
33 );
34
35 try
36 {
37 for (int index = AssemblyNames.GetUpperBound(0);
38 index >= AssemblyNames.GetLowerBound(0); index--)
39 {
40 // Create Native Image
41 NGenStartInfo.Arguments = String.Format("install \"{0}\"",
42 ExecutingAssemblyFullName.Replace(
43 ExecutingAssemblyName.Name,
44 AssemblyNames[index]));
45
46 NGenStartInfo.RedirectStandardOutput = true;
47 NGenStartInfo.UseShellExecute = false;
48
49 using (Process NGenProcess = Process.Start(NGenStartInfo))
50 {
51 NGenProcess.WaitForExit();
52 Context.LogMessage(NGenProcess.StandardOutput.ReadToEnd());
53 }
54 }
55 }
56 catch (Exception ex)
57 {
58 throw new InstallException(ex.Message, ex);
59 }
60 }
61
62 public override void Commit(System.Collections.IDictionary savedState)
63 {
64 base.Commit(savedState);
65
66 Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
67 AssemblyName ExecutingAssemblyName = ExecutingAssembly.GetName();
68 string ExecutingAssemblyFullName = ExecutingAssemblyName.FullName;
69
70 string GacFolder = String.Format(
71 "{0}\\GAC_{1}\\{2}\\{3}__{4}",
72 AssemblyCachePath,
73 ExecutingAssemblyName.ProcessorArchitecture,
74 ExecutingAssemblyName.Name,
75 ExecutingAssemblyName.Version,
76 ByteArrayToString(ExecutingAssemblyName.GetPublicKeyToken()));
77
78 string DebugSymbolsPath = Path.GetDirectoryName(ExecutingAssembly.Location);
79
80 try
81 {
82 for (int index = AssemblyNames.GetUpperBound(0);
83 index >= AssemblyNames.GetLowerBound(0); index--)
84 {
85 // Copy Debug Symbols to GAC Folder
86 string DebugSymbolsFile = AssemblyNames[index] + ".pdb";
87 string DestinationFile = Path.Combine(
88 GacFolder.Replace(
89 ExecutingAssemblyName.Name,
90 AssemblyNames[index]),
91 DebugSymbolsFile);
92
93 string SourceFile = Path.Combine(DebugSymbolsPath, DebugSymbolsFile);
94 if (File.Exists(SourceFile))
95 {
96 Context.LogMessage(String.Format(
97 "Copying {0} to {1}",
98 SourceFile,
99 DestinationFile));
100
101 File.Copy(SourceFile, DestinationFile, true);
102 }
103 }
104 }
105 catch (Exception ex)
106 {
107 throw new InstallException(ex.Message, ex);
108 }
109 }
110
111 public override void Uninstall(System.Collections.IDictionary savedState)
112 {
113 base.Uninstall(savedState);
114
115 Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
116 AssemblyName ExecutingAssemblyName = ExecutingAssembly.GetName();
117 string ExecutingAssemblyFullName = ExecutingAssemblyName.FullName;
118
119 ProcessStartInfo NGenStartInfo = new ProcessStartInfo(
120 Path.Combine( FrameworkPath, "ngen.exe" )
121 );
122
123 try
124 {
125 for (int index = AssemblyNames.GetLowerBound(0);
126 index <= AssemblyNames.GetUpperBound(0); index++)
127 {
128 // Create Native Image
129 NGenStartInfo.Arguments = String.Format("uninstall \"{0}\"",
130 ExecutingAssemblyFullName.Replace(
131 ExecutingAssemblyName.Name,
132 AssemblyNames[index]));
133
134 NGenStartInfo.RedirectStandardOutput = true;
135 NGenStartInfo.UseShellExecute = false;
136
137 using (Process NGenProcess = Process.Start(NGenStartInfo))
138 {
139 NGenProcess.WaitForExit();
140 Context.LogMessage(NGenProcess.StandardOutput.ReadToEnd());
141 }
142 }
143 }
144 catch (Exception ex)
145 {
146 throw new InstallException(ex.Message, ex);
147 }
148 }
149
150 public override void Rollback(System.Collections.IDictionary savedState)
151 {
152 base.Rollback(savedState);
153
154 Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
155 AssemblyName ExecutingAssemblyName = ExecutingAssembly.GetName();
156 string ExecutingAssemblyFullName = ExecutingAssemblyName.FullName;
157
158 ProcessStartInfo NGenStartInfo = new ProcessStartInfo(
159 Path.Combine( FrameworkPath, "ngen.exe" )
160 );
161
162 try
163 {
164 for (int index = AssemblyNames.GetUpperBound(0);
165 index >= AssemblyNames.GetLowerBound(0); index--)
166 {
167 // Create Native Image
168 NGenStartInfo.Arguments = String.Format("uninstall \"{0}\"",
169 ExecutingAssemblyFullName.Replace(
170 ExecutingAssemblyName.Name,
171 AssemblyNames[index]));
172
173 NGenStartInfo.RedirectStandardOutput = true;
174 NGenStartInfo.UseShellExecute = false;
175
176 using (Process NGenProcess = Process.Start(NGenStartInfo))
177 {
178 NGenProcess.WaitForExit();
179 Context.LogMessage(NGenProcess.StandardOutput.ReadToEnd());
180 }
181 }
182 }
183 catch (Exception ex)
184 {
185 throw new InstallException(ex.Message, ex);
186 }
187 }
188
189 private string ByteArrayToString(byte[] byteArray)
190 {
191 if (byteArray == null)
192 return null;
193
194 StringBuilder buffer = new StringBuilder(byteArray.Length);
195 for (int index = 0; index < byteArray.Length; index++)
196 {
197 buffer.AppendFormat("{0:x2}", byteArray[index]);
198 }
199
200 return buffer.ToString();
201 }
202
203 public enum ASM_CACHE_FLAGS
204 {
205 ASM_CACHE_ZAP = 0x01,
206 ASM_CACHE_GAC = 0x02,
207 ASM_CACHE_DOWNLOAD = 0x04,
208 ASM_CACHE_ROOT = 0x08
209 }