protected override ProjectUpgradeState UpgradeProjectCheck(ProjectRootElement projectXml, ProjectRootElement userProjectXml, Action<__VSUL_ERRORLEVEL, string> log, ref Guid projectFactory, ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport) {
            var envVarsProp = projectXml.Properties.FirstOrDefault(p => p.Name == NodejsConstants.EnvironmentVariables);
            if (envVarsProp != null && !string.IsNullOrEmpty(envVarsProp.Value)) {
                return ProjectUpgradeState.OneWayUpgrade;
            }

            return ProjectUpgradeState.NotNeeded;
        }
Exemple #2
0
 /// <summary>
 /// Determines whether a project needs to be upgraded.
 /// </summary>
 /// <param name="projectXml">
 /// The XML of the project file being upgraded.
 /// </param>
 /// <param name="userProjectXml">
 /// The XML of the user file being upgraded, or null if no user file
 /// exists.
 /// </param>
 /// <param name="log">
 /// Callback to log messages. These messages will be added to the
 /// migration log that is displayed after upgrading completes.
 /// </param>
 /// <param name="projectFactory">
 /// The project factory that will be used. This may be replaced with
 /// another Guid if a new project factory should be used to upgrade the
 /// project.
 /// </param>
 /// <param name="backupSupport">
 /// The level of backup support requested for the project. By default,
 /// the project file (and user file, if any) will be copied alongside
 /// the originals with ".old" added to the filenames.
 /// </param>
 /// <returns>
 /// The form of upgrade required.
 /// </returns>
 protected virtual ProjectUpgradeState UpgradeProjectCheck(
     ProjectRootElement projectXml,
     ProjectRootElement userProjectXml,
     Action <__VSUL_ERRORLEVEL, string> log,
     ref Guid projectFactory,
     ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
     )
 {
     return(ProjectUpgradeState.NotNeeded);
 }
        protected override ProjectUpgradeState UpgradeProjectCheck(
            ProjectRootElement projectXml,
            ProjectRootElement userProjectXml,
            Action <__VSUL_ERRORLEVEL, string> log,
            ref Guid projectFactory,
            ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
            )
        {
            Version version;

            // Web projects are incompatible with WDExpress/Shell
            ProjectPropertyElement projectType;

            if (!IsWebProjectSupported &&
                (projectType = projectXml.Properties.FirstOrDefault(p => p.Name == "ProjectTypeGuids")) != null)
            {
                var webProjectGuid = new Guid(PythonConstants.WebProjectFactoryGuid);
                if (projectType.Value
                    .Split(';')
                    .Select(s => {
                    Guid g;
                    return(Guid.TryParse(s, out g) ? g : Guid.Empty);
                })
                    .Contains(webProjectGuid)
                    )
                {
                    log(__VSUL_ERRORLEVEL.VSUL_ERROR, SR.GetString(SR.ProjectRequiresVWDExpress));
                    return(ProjectUpgradeState.Incompatible);
                }
            }

            var imports = new HashSet <string>(projectXml.Imports.Select(p => p.Project), StringComparer.OrdinalIgnoreCase);

            // Importing a targets file from 2.1 Beta
            if (imports.Contains(Ptvs21BetaBottleTargets) || imports.Contains(Ptvs21BetaFlaskTargets))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            // Only importing the Common targets and/or props.
            if (imports.Contains(CommonProps) || imports.Contains(CommonTargets) && imports.Count == 1)
            {
                return(ProjectUpgradeState.OneWayUpgrade);
            }

            // ToolsVersion less than 4.0 (or unspecified) is not supported, so
            // set it to 4.0.
            if (!Version.TryParse(projectXml.ToolsVersion, out version) ||
                version < new Version(4, 0))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            return(ProjectUpgradeState.NotNeeded);
        }
Exemple #4
0
        protected override ProjectUpgradeState UpgradeProjectCheck(
            ProjectRootElement projectXml,
            ProjectRootElement userProjectXml,
            Action <__VSUL_ERRORLEVEL, string> log,
            ref Guid projectFactory,
            ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
            )
        {
            Version version;

            // Referencing an interpreter by GUID
            if (projectXml.Properties.Where(p => p.Name == "InterpreterId").Any(IsGuidValue) ||
                projectXml.ItemGroups.SelectMany(g => g.Items)
                .Where(i => i.ItemType == "InterpreterReference")
                .Any(IsGuidValue) ||
                projectXml.ItemGroups.SelectMany(g => g.Items)
                .Where(i => i.ItemType == "Interpreter")
                .SelectMany(i => i.Metadata.Where(m => m.Name == "BaseInterpreter"))
                .Any(IsGuidValue)
                )
            {
                return(ProjectUpgradeState.OneWayUpgrade);
            }

            // Includes imports from PTVS 2.2
            if (projectXml.Properties.Any(IsPtvsTargetsFileProperty))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            var imports = new HashSet <string>(projectXml.Imports.Select(p => p.Project), StringComparer.OrdinalIgnoreCase);

            // Importing a targets file from 2.1 Beta
            if (imports.Contains(Ptvs21BetaBottleTargets) || imports.Contains(Ptvs21BetaFlaskTargets))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            // Only importing the Common targets and/or props.
            if (imports.Contains(CommonProps) || imports.Contains(CommonTargets) && imports.Count == 1)
            {
                return(ProjectUpgradeState.OneWayUpgrade);
            }

            // ToolsVersion less than 4.0 (or unspecified) is not supported, so
            // set it to 4.0.
            if (!Version.TryParse(projectXml.ToolsVersion, out version) ||
                version < new Version(4, 0))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            return(ProjectUpgradeState.NotNeeded);
        }
        protected override ProjectUpgradeState UpgradeProjectCheck(
            ProjectRootElement projectXml,
            ProjectRootElement userProjectXml,
            Action <__VSUL_ERRORLEVEL, string> log,
            ref Guid projectFactory,
            ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
            )
        {
            Version version;

            if (!Version.TryParse(projectXml.ToolsVersion, out version) ||
                version < new Version(4, 0))
            {
                return(ProjectUpgradeState.SafeRepair);
            }

            return(ProjectUpgradeState.NotNeeded);
        }
Exemple #6
0
        private int NormalizeUpgradeFlag(uint upgradeFlag, out __VSPPROJECTUPGRADEVIAFACTORYFLAGS flag, ref string copyLocation)
        {
            flag = (__VSPPROJECTUPGRADEVIAFACTORYFLAGS)upgradeFlag;
            // normalize flags
            switch (flag)
            {
            case __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP:
            case __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP:
                break;

            default:
                // ignore unknown flags
                flag &= (__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP | __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP);
                break;
            }

            //if copy upgrade, then we need a copy location that ends in a backslash.
            if (HasCopyBackupFlag(flag))
            {
                if (string.IsNullOrEmpty(copyLocation) || copyLocation[copyLocation.Length - 1] != '\\')
                {
                    if (HasSxSBackupFlag(flag))
                    {
                        //if both SxS AND CopyBack were specified, then fall back to SxS
                        flag &= ~__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP;
                    }
                    else
                    {
                        //Only CopyBackup was specified and an invalid backup location was given, so bail
                        return(VSConstants.E_INVALIDARG);
                    }
                }
                else
                {
                    //Favor copy backup to SxS backup
                    flag &= ~__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP;
                }
            }
            return(VSConstants.S_OK);
        }
 /// <summary>
 /// Helper for checking if flag has PUVFF_COPYBACKUP value
 /// </summary>
 private static bool HasCopyBackupFlag(__VSPPROJECTUPGRADEVIAFACTORYFLAGS flag)
 {
     return flag.HasFlag(__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP);
 }
        private void BackupProjectFilesIfNeeded(
            string projectFilePath, 
            ProjectUpgradeLogger logger, 
            __VSPPROJECTUPGRADEVIAFACTORYFLAGS flag, 
            string copyLocation, 
            ProjectRootElement convertedProject
            )
        {
            var projectName = Path.GetFileNameWithoutExtension(projectFilePath);
            var projectFileName = Path.GetFileName(projectFilePath);

            if (HasCopyBackupFlag(flag) || HasSxSBackupFlag(flag))
            {
                if (HasSxSBackupFlag(flag) && !Directory.Exists(copyLocation))
                {
                    Debug.Assert(false, "Env should create the directory for us");
                    throw new ProjectUpgradeFailedException();
                }

                // copy project file
                {
                    var targetFilePath = Path.Combine(copyLocation, projectFileName);
                    if (HasSxSBackupFlag(flag))
                    {
                        bool ignored;
                        targetFilePath = GetUniqueFileName(targetFilePath + ".old", out ignored);
                    }

                    try
                    {
                        File.Copy(projectFilePath, targetFilePath);
                        logger.LogInfo(SR.GetString(SR.ProjectBackupSuccessful, targetFilePath));
                    }
                    catch (Exception ex)
                    {
                        var message = SR.GetString(SR.ErrorMakingProjectBackup, targetFilePath);
                        throw new ProjectUpgradeFailedException(string.Format("{0} : {1}", message, ex.Message));
                    }
                }

                if (HasCopyBackupFlag(flag))
                {
                    //Now iterate through the project items and copy them to the new location
                    //All projects under the solution retain its folder hierarchy
                    var types = new[] { "Compile", "None", "Content", "EmbeddedResource", "Resource", "BaseApplicationManifest", "ApplicationDefinition", "Page" };

                    var metadataLookup =
                            convertedProject
                            .Items
                            .GroupBy(i => i.ItemType)
                            .ToDictionary(x => x.Key);

                    var sourceProjectDir = Path.GetDirectoryName(projectFilePath);
                    foreach (var ty in types)
                    {
                        if (metadataLookup.ContainsKey(ty))
                        {
                            foreach (var item in metadataLookup[ty])
                            {
                                var linkMetadataElement = item.Metadata.FirstOrDefault(me => me.Name == "Link");
                                string linked = linkMetadataElement != null && !string.IsNullOrEmpty(linkMetadataElement.Value) ? linkMetadataElement.Value : null;
                                
                                var include = item.Include;

                                Debug.Assert(!string.IsNullOrEmpty(include));

                                string sourceFilePath;

                                var targetFileName = Path.Combine(copyLocation, linked ?? include);

                                if (Path.IsPathRooted(include))
                                {
                                    //if the path is fully qualified already, then just use it
                                    sourceFilePath = include;
                                }
                                else
                                {
                                    //otherwise tack the filename on to the path
                                    sourceFilePath = Path.Combine(sourceProjectDir, include);
                                }
                                if (linked != null && include[0] == '.')
                                {
                                    //if linked file up a level (or more), then turn it into a path without the ..\ in the middle
                                    sourceFilePath = Path.GetFullPath(sourceFilePath);
                                }

                                bool initiallyUnique;
                                targetFileName = GetUniqueFileName(targetFileName, out initiallyUnique);
                                if (!initiallyUnique)
                                {
                                    logger.LogInfo(SR.GetString(SR.BackupNameConflict, targetFileName));
                                }

                                //Warn user in upgrade log if linked files are used "project may not build"
                                if (linked != null && HasCopyBackupFlag(flag))
                                {
                                    logger.LogWarning(SR.GetString(SR.ProjectContainsLinkedFile, targetFileName));
                                }

                                // ensure target folder exists
                                Directory.CreateDirectory(Path.GetDirectoryName(targetFileName));

                                try
                                {
                                    File.Copy(sourceFilePath, targetFileName);
                                    logger.LogInfo(SR.GetString(SR.BackupSuccessful, targetFileName));
                                }
                                catch (Exception ex)
                                {
                                    var message = SR.GetString(SR.ErrorMakingBackup, targetFileName);
                                    logger.LogError(string.Format("{0} : {1}", message, ex.Message));
                                }                                
                            }
                        }
                    }
                }
            }
        }
        private int NormalizeUpgradeFlag(uint upgradeFlag, out __VSPPROJECTUPGRADEVIAFACTORYFLAGS flag, ref string copyLocation)
        {
            flag = (__VSPPROJECTUPGRADEVIAFACTORYFLAGS)upgradeFlag;
            // normalize flags
            switch (flag)
            {
                case __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP:
                case __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP:
                    break;
                default:
                    // ignore unknown flags
                    flag &= (__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP | __VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP);
                    break;
            }

            //if copy upgrade, then we need a copy location that ends in a backslash.
            if (HasCopyBackupFlag(flag))
            {
                if (string.IsNullOrEmpty(copyLocation) || copyLocation[copyLocation.Length - 1] != '\\')
                {                    
                    if (HasSxSBackupFlag(flag))
                    {
                        //if both SxS AND CopyBack were specified, then fall back to SxS
                        flag &= ~__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP;
                    }
                    else
                    {
                        //Only CopyBackup was specified and an invalid backup location was given, so bail
                        return VSConstants.E_INVALIDARG;
                    }
                }
                else
                {
                    //Favor copy backup to SxS backup
                    flag &= ~__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_SXSBACKUP;
                }
            }
            return VSConstants.S_OK;
        }
        protected override ProjectUpgradeState UpgradeProjectCheck(
            ProjectRootElement projectXml,
            ProjectRootElement userProjectXml,
            Action<__VSUL_ERRORLEVEL, string> log,
            ref Guid projectFactory,
            ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
        ) {
            Version version;

            // Web projects are incompatible with WDExpress/Shell
            ProjectPropertyElement projectType;
            if (!IsWebProjectSupported &&
                (projectType = projectXml.Properties.FirstOrDefault(p => p.Name == "ProjectTypeGuids")) != null) {
                var webProjectGuid = new Guid(PythonConstants.WebProjectFactoryGuid);
                if (projectType.Value
                    .Split(';')
                    .Select(s => {
                        Guid g;
                        return Guid.TryParse(s, out g) ? g : Guid.Empty;
                    })
                    .Contains(webProjectGuid)
                ) {
                    log(__VSUL_ERRORLEVEL.VSUL_ERROR, SR.GetString(SR.ProjectRequiresVWDExpress));
                    return ProjectUpgradeState.Incompatible;
                }
            }

            var imports = new HashSet<string>(projectXml.Imports.Select(p => p.Project), StringComparer.OrdinalIgnoreCase);
            // Importing a targets file from 2.1 Beta
            if (imports.Contains(Ptvs21BetaBottleTargets) || imports.Contains(Ptvs21BetaFlaskTargets)) {
                return ProjectUpgradeState.SafeRepair;
            }

            // Only importing the Common targets and/or props.
            if (imports.Contains(CommonProps) || imports.Contains(CommonTargets) && imports.Count == 1) {
                return ProjectUpgradeState.OneWayUpgrade;
            }

            // ToolsVersion less than 4.0 (or unspecified) is not supported, so
            // set it to 4.0.
            if (!Version.TryParse(projectXml.ToolsVersion, out version) ||
                version < new Version(4, 0)) {
                return ProjectUpgradeState.SafeRepair;
            }

            return ProjectUpgradeState.NotNeeded;
        }
Exemple #11
0
 /// <summary>
 /// Helper for checking if flag has PUVFF_COPYBACKUP value
 /// </summary>
 private static bool HasCopyBackupFlag(__VSPPROJECTUPGRADEVIAFACTORYFLAGS flag)
 {
     return(flag.HasFlag(__VSPPROJECTUPGRADEVIAFACTORYFLAGS.PUVFF_COPYBACKUP));
 }
Exemple #12
0
        private void BackupProjectFilesIfNeeded(
            string projectFilePath,
            ProjectUpgradeLogger logger,
            __VSPPROJECTUPGRADEVIAFACTORYFLAGS flag,
            string copyLocation,
            ProjectRootElement convertedProject
            )
        {
            var projectName     = Path.GetFileNameWithoutExtension(projectFilePath);
            var projectFileName = Path.GetFileName(projectFilePath);

            if (HasCopyBackupFlag(flag) || HasSxSBackupFlag(flag))
            {
                if (HasSxSBackupFlag(flag) && !Directory.Exists(copyLocation))
                {
                    Debug.Assert(false, "Env should create the directory for us");
                    throw new ProjectUpgradeFailedException();
                }

                // copy project file
                {
                    var targetFilePath = Path.Combine(copyLocation, projectFileName);
                    if (HasSxSBackupFlag(flag))
                    {
                        bool ignored;
                        targetFilePath = GetUniqueFileName(targetFilePath + ".old", out ignored);
                    }

                    try
                    {
                        File.Copy(projectFilePath, targetFilePath);
                        logger.LogInfo(SR.GetString(SR.ProjectBackupSuccessful, targetFilePath));
                    }
                    catch (Exception ex)
                    {
                        var message = SR.GetString(SR.ErrorMakingProjectBackup, targetFilePath);
                        throw new ProjectUpgradeFailedException(string.Format("{0} : {1}", message, ex.Message));
                    }
                }

                if (HasCopyBackupFlag(flag))
                {
                    //Now iterate through the project items and copy them to the new location
                    //All projects under the solution retain its folder hierarchy
                    var types = new[] { "Compile", "None", "Content", "EmbeddedResource", "Resource", "BaseApplicationManifest", "ApplicationDefinition", "Page" };

                    var metadataLookup =
                        convertedProject
                        .Items
                        .GroupBy(i => i.ItemType)
                        .ToDictionary(x => x.Key);

                    var sourceProjectDir = Path.GetDirectoryName(projectFilePath);
                    foreach (var ty in types)
                    {
                        if (metadataLookup.ContainsKey(ty))
                        {
                            foreach (var item in metadataLookup[ty])
                            {
                                var    linkMetadataElement = item.Metadata.FirstOrDefault(me => me.Name == "Link");
                                string linked = linkMetadataElement != null && !string.IsNullOrEmpty(linkMetadataElement.Value) ? linkMetadataElement.Value : null;

                                var include = item.Include;

                                Debug.Assert(!string.IsNullOrEmpty(include));

                                string sourceFilePath;

                                var targetFileName = Path.Combine(copyLocation, linked ?? include);

                                if (Path.IsPathRooted(include))
                                {
                                    //if the path is fully qualified already, then just use it
                                    sourceFilePath = include;
                                }
                                else
                                {
                                    //otherwise tack the filename on to the path
                                    sourceFilePath = Path.Combine(sourceProjectDir, include);
                                }
                                if (linked != null && include[0] == '.')
                                {
                                    //if linked file up a level (or more), then turn it into a path without the ..\ in the middle
                                    sourceFilePath = Path.GetFullPath(sourceFilePath);
                                }

                                bool initiallyUnique;
                                targetFileName = GetUniqueFileName(targetFileName, out initiallyUnique);
                                if (!initiallyUnique)
                                {
                                    logger.LogInfo(SR.GetString(SR.BackupNameConflict, targetFileName));
                                }

                                //Warn user in upgrade log if linked files are used "project may not build"
                                if (linked != null && HasCopyBackupFlag(flag))
                                {
                                    logger.LogWarning(SR.GetString(SR.ProjectContainsLinkedFile, targetFileName));
                                }

                                // ensure target folder exists
                                Directory.CreateDirectory(Path.GetDirectoryName(targetFileName));

                                try
                                {
                                    File.Copy(sourceFilePath, targetFileName);
                                    logger.LogInfo(SR.GetString(SR.BackupSuccessful, targetFileName));
                                }
                                catch (Exception ex)
                                {
                                    var message = SR.GetString(SR.ErrorMakingBackup, targetFileName);
                                    logger.LogError(string.Format("{0} : {1}", message, ex.Message));
                                }
                            }
                        }
                    }
                }
            }
        }
Exemple #13
0
 /// <summary>
 /// Determines whether a project needs to be upgraded.
 /// </summary>
 /// <param name="projectXml">
 /// The XML of the project file being upgraded.
 /// </param>
 /// <param name="userProjectXml">
 /// The XML of the user file being upgraded, or null if no user file
 /// exists.
 /// </param>
 /// <param name="log">
 /// Callback to log messages. These messages will be added to the
 /// migration log that is displayed after upgrading completes.
 /// </param>
 /// <param name="projectFactory">
 /// The project factory that will be used. This may be replaced with
 /// another Guid if a new project factory should be used to upgrade the
 /// project.
 /// </param>
 /// <param name="backupSupport">
 /// The level of backup support requested for the project. By default,
 /// the project file (and user file, if any) will be copied alongside
 /// the originals with ".old" added to the filenames.
 /// </param>
 /// <returns>
 /// The form of upgrade required.
 /// </returns>
 protected virtual ProjectUpgradeState UpgradeProjectCheck(
     ProjectRootElement projectXml,
     ProjectRootElement userProjectXml,
     Action<__VSUL_ERRORLEVEL, string> log,
     ref Guid projectFactory,
     ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
 ) {
     return ProjectUpgradeState.NotNeeded;
 }
        protected override ProjectUpgradeState UpgradeProjectCheck(ProjectRootElement projectXml, ProjectRootElement userProjectXml, Action <__VSUL_ERRORLEVEL, string> log, ref Guid projectFactory, ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport)
        {
            var envVarsProp = projectXml.Properties.FirstOrDefault(p => p.Name == NodejsConstants.EnvironmentVariables);

            if (envVarsProp != null && !string.IsNullOrEmpty(envVarsProp.Value))
            {
                return(ProjectUpgradeState.OneWayUpgrade);
            }

            return(ProjectUpgradeState.NotNeeded);
        }
        protected override ProjectUpgradeState UpgradeProjectCheck(
            ProjectRootElement projectXml,
            ProjectRootElement userProjectXml,
            Action<__VSUL_ERRORLEVEL, string> log,
            ref Guid projectFactory,
            ref __VSPPROJECTUPGRADEVIAFACTORYFLAGS backupSupport
        ) {
            Version version;

            // Referencing an interpreter by GUID
            if (projectXml.Properties.Where(p => p.Name == "InterpreterId").Any(IsGuidValue) ||
                projectXml.ItemGroups.SelectMany(g => g.Items)
                    .Where(i => i.ItemType == "InterpreterReference")
                    .Any(IsGuidValue) ||
                projectXml.ItemGroups.SelectMany(g => g.Items)
                    .Where(i => i.ItemType == "Interpreter")
                    .SelectMany(i => i.Metadata.Where(m => m.Name == "BaseInterpreter"))
                    .Any(IsGuidValue)
            ) {
                return ProjectUpgradeState.OneWayUpgrade;
            }

            var imports = new HashSet<string>(projectXml.Imports.Select(p => p.Project), StringComparer.OrdinalIgnoreCase);
            // Only importing the Common targets and/or props.
            if (imports.Contains(CommonProps) || imports.Contains(CommonTargets) && imports.Count == 1) {
                return ProjectUpgradeState.OneWayUpgrade;
            }

            // Includes imports from PTVS 2.2
            if (projectXml.Properties.Any(IsPtvsTargetsFileProperty)) {
                return ProjectUpgradeState.SafeRepair;
            }

            // Uses web or Django launcher and has no WebBrowserUrl property
            if (projectXml.Properties.Where(p => p.Name == "LaunchProvider")
                    .Any(p => p.Value == "Web launcher" || p.Value == "Django launcher") &&
                !projectXml.Properties.Any(p => p.Name == "WebBrowserUrl")) {
                return ProjectUpgradeState.SafeRepair;
            }

            // Importing a targets file from 2.1 Beta
            if (imports.Contains(Ptvs21BetaBottleTargets) || imports.Contains(Ptvs21BetaFlaskTargets)) {
                return ProjectUpgradeState.SafeRepair;
            }

            // ToolsVersion less than 4.0 (or unspecified) is not supported, so
            // set it to 4.0.
            if (!Version.TryParse(projectXml.ToolsVersion, out version) ||
                version < new Version(4, 0)) {
                return ProjectUpgradeState.SafeRepair;
            }

            return ProjectUpgradeState.NotNeeded;
        }