C# method to find installed .NET runtime versions from the Registry (Windows only).

private static string[] _installedRuntimeVersions;

/// <summary>
/// Find Installed .NET Runtime Versions (Windows only)
/// </summary>
private static string[] FindInstalledRuntimeVersions()
{
    //
    // Initialize array of installed runtime versions if this the
    // first run.
    //

    if (_installedRuntimeVersions == null)
    {
        ArrayList list = new ArrayList(5);

        //
        // The list of installed versions appear as sub-keys of
        // HKLM\SOFTWARE\Microsoft\.NETFramework\policy.
        //

        using(RegistryKey frameworkKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework"))
        {
            //
            // The InstallRoot value yields the root of where
            // .NET Framework installations can be found. This
            // will be used to verify each version we find under
            // the policy key.
            //

            string installRootPath = frameworkKey.GetValue("InstallRoot", string.Empty).ToString();

            if (installRootPath.Length > 0 && Directory.Exists(installRootPath))
            {
                using(RegistryKey policyKey = frameworkKey.OpenSubKey("policy"))
                {
                    foreach (string policySubKeyName in policyKey.GetSubKeyNames())
                    {
                        //
                        // Only process keys starting with a small "v", assuming a
                        // format of v#.# where # are major andminor version,
                        // respectively.
                        //

                        if (policySubKeyName.Length > 1 && policySubKeyName[0] == 'v')
                        {
                            using(RegistryKey versionKey =  policyKey.OpenSubKey(policySubKeyName))
                            {
                                //
                                // Get value names under the policy version
                                // key since that is where one finds the
                                // build number. We will try to parse each
                                // name as a number and pick the first one
                                // that works!
                                //

                                foreach (string valueName in versionKey.GetValueNames())
                                {
                                    string version = string.Empty;
                                    try
                                    {
                                        int.Parse(valueName, NumberStyles.None, CultureInfo.InvariantCulture);
                                        version = policySubKeyName + "." + valueName;
                                    }
                                    catch (FormatException) { /* ignore */ }

                                    if (version.Length > 0)
                                    {
                                        string versionPath = Path.Combine(installRootPath, version);
                                        if (Directory.Exists(versionPath))
                                        {
                                            list.Add(version);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        _installedRuntimeVersions = (string[]) list.ToArray(typeof(string));
    }

    //
    // We clone the array on each call so that the private copy is
    // not compromised.
    //

    return (string[]) _installedRuntimeVersions.Clone();
}