How can I programmatically determine the effect of each StartUp project in a solution?
In the "Solutions-> Properties" section, I can install several startup projects: 
I know that I can get a list of projects marked with "Start" (using EnvDTE: solution.SolutionBuild.StartupProjects ), but how can I get a list of projects whose action is "Start without debugging"? They do not appear in the list.
I don't think this is documented and available officially, but here is some information:
This is stored in the solution .SUO file using the built-in Visual Studio package. The SUO file has an OLE storage format. You can view it with a tool such as OpenMCDF (it has a sample explorer). In this file, you will see a stream called "
SolutionConfiguration" that contains the dwStartupOpt token, and then the information you are looking for. The stream itself has its own binary format.The same information is available from VS through the IVsPersistSolutionProps interface . You need to get a pointer to it from one of the downloaded packages (for example, listing the packages using the IVsShell.GetPackageEnum method . One package will support the IVsPersistSolutionProps interface with the
SolutionConfigurationstream.
However, whichever method you choose, you, in turn, will parse the "SolutionConfiguration" stream manually. I present here a method that simply opens the SUO file and breaks the bits "manually", so it works outside of VS.
Here is a utility class that parses the "SolutionConfiguration" stream:
public sealed class StartupOptions { private StartupOptions() { } public static IDictionary<Guid, int> ReadStartupOptions(string filePath) { if (filePath == null) throw new ArgumentNullException("filePath"); // look for this token in the file const string token = "dwStartupOpt\0="; byte[] tokenBytes = Encoding.Unicode.GetBytes(token); Dictionary<Guid, int> dic = new Dictionary<Guid, int>(); byte[] bytes; using (MemoryStream stream = new MemoryStream()) { CompoundFileUtilities.ExtractStream(filePath, "SolutionConfiguration", stream); bytes = stream.ToArray(); } int i = 0; do { bool found = true; for (int j = 0; j < tokenBytes.Length; j++) { if (bytes[i + j] != tokenBytes[j]) { found = false; break; } } if (found) { // back read the corresponding project guid // guid is formatted as {guid} // len to read is Guid length* 2 and there are two offset bytes between guid and startup options token byte[] guidBytes = new byte[38 * 2]; Array.Copy(bytes, i - guidBytes.Length - 2, guidBytes, 0, guidBytes.Length); Guid guid = new Guid(Encoding.Unicode.GetString(guidBytes)); // skip VT_I4 int options = BitConverter.ToInt32(bytes, i + tokenBytes.Length + 2); dic[guid] = options; } i++; } while (i < bytes.Length); return dic; } } The following is a small utility for reading a composite stream (there is no need for an external library):
public static class CompoundFileUtilities { public static void ExtractStream(string filePath, string streamName, string streamPath) { if (filePath == null) throw new ArgumentNullException("filePath"); if (streamName == null) throw new ArgumentNullException("streamName"); if (streamPath == null) throw new ArgumentNullException("streamPath"); using (FileStream output = new FileStream(streamPath, FileMode.Create)) { ExtractStream(filePath, streamName, output); } } public static void ExtractStream(string filePath, string streamName, Stream output) { if (filePath == null) throw new ArgumentNullException("filePath"); if (streamName == null) throw new ArgumentNullException("streamName"); if (output == null) throw new ArgumentNullException("output"); IStorage storage; int hr = StgOpenStorage(filePath, null, STGM.READ | STGM.SHARE_DENY_WRITE, IntPtr.Zero, 0, out storage); if (hr != 0) throw new Win32Exception(hr); try { IStream stream; hr = storage.OpenStream(streamName, IntPtr.Zero, STGM.READ | STGM.SHARE_EXCLUSIVE, 0, out stream); if (hr != 0) throw new Win32Exception(hr); int read = 0; IntPtr readPtr = Marshal.AllocHGlobal(Marshal.SizeOf(read)); try { byte[] bytes = new byte[0x1000]; do { stream.Read(bytes, bytes.Length, readPtr); read = Marshal.ReadInt32(readPtr); if (read == 0) break; output.Write(bytes, 0, read); } while(true); } finally { Marshal.FreeHGlobal(readPtr); Marshal.ReleaseComObject(stream); } } finally { Marshal.ReleaseComObject(storage); } } [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IStorage { void Unimplemented0(); [PreserveSig] int OpenStream([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr reserved1, STGM grfMode, uint reserved2, out IStream ppstm); // other methods not declared for simplicity } [Flags] private enum STGM { READ = 0x00000000, SHARE_DENY_WRITE = 0x00000020, SHARE_EXCLUSIVE = 0x00000010, // other values not declared for simplicity } [DllImport("ole32.dll")] private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstgOpen); } And a sample showing project hints related to launch options:
static void SafeMain(string[] args) { foreach (var kvp in StartupOptions.ReadStartupOptions("mySample.suo")) { if ((kvp.Value & 1) != 0) { Console.WriteLine("Project " + kvp.Key + " has option Start"); } if ((kvp.Value & 2) != 0) { Console.WriteLine("Project " + kvp.Key + " has option Start with debugging"); } } }