Visual Studio - Get the startup project programmatically

1

I'm building a .Net application that should import a VS2010/2008 solution and find the start up project by reading the .suo file. I looked at How do I programmatically find out the Action of each StartUp Project in a solution? but the solution mentioned works only if I select Multiple Startup Projects in the Solution Properties window. My solution does not have multiple startup project. Is there a way to find the startup project when the solution has only 1 startup proj?

Here's the code

public static class StartUpProjectHelper
    {

        public static FileInfo GetStartUpProject(FileInfo solutionFile)
        {
            FileInfo startUpProject = null;

            string suoFile = solutionFile.FullName.Substring(0, solutionFile.FullName.Length - 4) + ".suo";

            string guid = null;

            bool found = false;

            foreach (var kvp in ReadStartupOptions(suoFile))
            {
                if (((kvp.Value & 1) != 0 || (kvp.Value & 2) != 0) && !found)
                {
                    guid = kvp.Key.ToString();

                    found = true;
                }
            }

            if (!string.IsNullOrEmpty(guid))
            {
                string projectname = GetProjectNameFromGuid(solutionFile, guid).Trim().TrimStart('\"').TrimEnd('\"');

                startUpProject = new FileInfo(Path.Combine(solutionFile.DirectoryName, projectname));
            }

            return startUpProject;
        }


        public static string GetProjectNameFromGuid(FileInfo solutionFile, string guid)
        {
            string projectName = null;

            using (var reader = new StreamReader(solutionFile.FullName))
            {
                string line;

                bool found = false;

                while ((line = reader.ReadLine()) != null && !found)
                {
                    //  sample format
                    //Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saltus.digiTICKET.laptop", "digiTICKET.laptop\Saltus.digiTICKET.laptop.csproj", 
                    //"{236D51A1-DEB7-41C3-A4C1-1D16D0A85382}" EndProject

                    if ((line.IndexOf(guid.ToUpper()) > -1) && line.Contains(",") && line.Contains("="))
                    {
                        projectName = line.Split(',')[1].Split(',')[0];

                        found = true;
                    }
                }
            }

            return projectName;
        }

        ////  from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut
        public static IDictionary<Guid, int> ReadStartupOptions(string filePath)
        {
            if (filePath == null)
            {
                throw new InvalidOperationException("No file selected");
            }


            // look for this token in the file
            const string token = "dwStartupOpt\0=";

            byte[] tokenBytes = Encoding.Unicode.GetBytes(token);

            var dic = new Dictionary<Guid, int>();

            byte[] bytes;

            using (var stream = new MemoryStream())
            {
                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
                    var guidBytes = new byte[38*2];
                    Array.Copy(bytes, i - guidBytes.Length - 2, guidBytes, 0, guidBytes.Length);
                    var 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;
        }

        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
                {
                    var 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);
            }
        }

        [DllImport("ole32.dll")]
        private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
                                                 IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved,
                                                 out IStorage ppstgOpen);

        #region Nested type: IStorage

        [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
        }

        #endregion

        #region Nested type: STGM

        [Flags]
        private enum STGM
        {
            READ = 0x00000000,
            SHARE_DENY_WRITE = 0x00000020,
            SHARE_EXCLUSIVE = 0x00000010,
            // other values not declared for simplicity
        }

        #endregion
    }

    //// from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut``
c#
.net
asked on Stack Overflow Jun 3, 2016 by user678229 • edited Feb 6, 2020 by Twenty

1 Answer

3

Here's the solution. It is mostly based on @SimonMourier post, but instead of searching for "dwStartupOpt\0=", I'm looking for "StartupProject\0=&\0". This token occurs only once in the .suo file and is followed by the startup project's GUID.

public static FileInfo GetStartUpProject(FileInfo solutionFile)
{
    FileInfo startUpProject = null;

    string projectName = Path.GetFileNameWithoutExtension(solutionFile.FullName);

    FileInfo suoFileInfo = new FileInfo(Path.Combine(solutionFile.Directory.FullName, string.Format(projectName + "{0}", ".suo")));

    string guid = ReadStartupOptions(suoFileInfo.FullName).ToString();

    if (!string.IsNullOrEmpty(guid))
    {
        string projectname = GetProjectNameFromGuid(solutionFile, guid).Trim().TrimStart('\"').TrimEnd('\"');

        startUpProject = new FileInfo(Path.Combine(solutionFile.DirectoryName, projectname));
    }

    return startUpProject;
}


public static string GetProjectNameFromGuid(FileInfo solutionFile, string guid)
{
    string projectName = null;

    using (var reader = new StreamReader(solutionFile.FullName))
    {
        string line;

        bool found = false;

        while ((line = reader.ReadLine()) != null && !found)
        {


            if ((line.IndexOf(guid.ToUpper()) > -1) && line.Contains(",") && line.Contains("="))
            {
                projectName = line.Split(',')[1].Split(',')[0];

                found = true;
            }
        }
    }

    return projectName;
}

//  from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut
public static Guid ReadStartupOptions(string filePath)
{
    Guid guid = new Guid();

    if (filePath == null)
    {
        throw new InvalidOperationException("No file selected");
    }

    const string token = "StartupProject\0=&\0";

    byte[] tokenBytes = Encoding.Unicode.GetBytes(token);

    byte[] bytes;

    using (var stream = new MemoryStream())
    {
        ExtractStream(filePath, "SolutionConfiguration", stream);

        bytes = stream.ToArray();
    }

    var guidBytes = new byte[36 * 2]; 

    for (int i2 = 0; i2 < bytes.Length; i2++)
    {
        if (bytes.Skip(i2).Take(tokenBytes.Length).SequenceEqual(tokenBytes))
        {
            Array.Copy(bytes, i2 + tokenBytes.Length + 2, guidBytes, 0, guidBytes.Length);

            guid = new Guid(Encoding.Unicode.GetString(guidBytes));

            break;
        }
    }

    return guid;

}

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
        {
            var 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);
    }
}

[DllImport("ole32.dll")]
private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
                                         IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved,
                                         out IStorage ppstgOpen);

#region Nested type: IStorage

[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
}

#endregion

#region Nested type: STGM

[Flags]
private enum STGM
{
    READ = 0x00000000,
    SHARE_DENY_WRITE = 0x00000020,
    SHARE_EXCLUSIVE = 0x00000010,
    // other values not declared for simplicity
}

#endregion    
answered on Stack Overflow Jun 3, 2016 by user678229 • edited Feb 10, 2020 by Twenty

User contributions licensed under CC BY-SA 3.0