Repository: dotnet/sdk
Pull request #46915: Add basic support for 'dotnet run file.cs'
Author: login: jjonescz
Labels: name: Area-run-file
description: Items related to the "dotnet run <file>" effort
Created: 2025-02-18T14:21:30Z | Merged: 2025-03-12T10:09:54Z

Spec: https://github.com/dotnet/sdk/pull/47020 ([rendered markdown](https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md))


Diff:
diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
index 6ecc1f539a3c..496097bb50ce 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
@@ -36,13 +36,7 @@ public static string MSBuildVersion
     // True if, given current state of the class, MSBuild would be executed in its own process.
     public bool ExecuteMSBuildOutOfProc => _forwardingApp != null;
 
-    private readonly Dictionary<string, string> _msbuildRequiredEnvironmentVariables =
-        new()
-        {
-            { "MSBuildExtensionsPath", MSBuildExtensionsPathTestHook ?? AppContext.BaseDirectory },
-            { "MSBuildSDKsPath", GetMSBuildSDKsPath() },
-            { "DOTNET_HOST_PATH", GetDotnetPath() },
-        };
+    private readonly Dictionary<string, string> _msbuildRequiredEnvironmentVariables = GetMSBuildRequiredEnvironmentVariables();
 
     private readonly List<string> _msbuildRequiredParameters =
         [ "-maxcpucount", "-verbosity:m" ];
@@ -200,6 +194,16 @@ private static string GetDotnetPath()
         return new Muxer().MuxerPath;
     }
 
+    internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
+    {
+        return new()
+        {
+            { "MSBuildExtensionsPath", MSBuildExtensionsPathTestHook ?? AppContext.BaseDirectory },
+            { "MSBuildSDKsPath", GetMSBuildSDKsPath() },
+            { "DOTNET_HOST_PATH", GetDotnetPath() },
+        };
+    }
+
     private static bool IsRestoreSources(string arg)
     {
         return arg.StartsWith("/p:RestoreSources=", StringComparison.OrdinalIgnoreCase) ||
diff --git a/src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
new file mode 100644
index 000000000000..c48d0e210556
--- /dev/null
+++ b/src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
@@ -0,0 +1,200 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, ILogger consoleLogger)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Set up MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            // Like in MSBuild, only the last binary logger is used.
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <!-- We need to explicitly import Sdk props/targets so we can override the targets below. -->
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net10.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+
+                    <EnableDefaultItems>false</EnableDefaultItems>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
+                -->
+
+                <Target Name="_FilterRestoreGraphProjectInputItems"
+                        DependsOnTargets="_LoadRestoreGraphEntryPoints"
+                        Returns="@(FilteredRestoreGraphProjectInputItems)">
+                    <ItemGroup>
+                        <FilteredRestoreGraphProjectInputItems Include="@(RestoreGraphProjectInputItems)" />
+                    </ItemGroup>
+                </Target>
+
+                <Target Name="_GetAllRestoreProjectPathItems"
+                        DependsOnTargets="_FilterRestoreGraphProjectInputItems"
+                        Returns="@(_RestoreProjectPathItems)">
+                    <ItemGroup>
+                        <_RestoreProjectPathItems Include="@(FilteredRestoreGraphProjectInputItems)" />
+                    </ItemGroup>
+                </Target>
+
+                <Target Name="_GenerateRestoreGraph"
+                        DependsOnTargets="_FilterRestoreGraphProjectInputItems;_GetAllRestoreProjectPathItems;_GenerateRestoreGraphProjectEntry;_GenerateProjectRestoreGraph"
+                        Returns="@(_RestoreGraphEntry)">
+                    <!-- Output from dependency _GenerateRestoreGraphProjectEntry and _GenerateProjectRestoreGraph -->
+                </Target>
+            </Project>
+            """;
+        ProjectRootElement projectRoot;
+        using (var xmlReader = XmlReader.Create(new StringReader(projectFileText)))
+        {
+            projectRoot = ProjectRootElement.Create(xmlReader, projectCollection);
+        }
+        projectRoot.AddItem(itemType: "Compile", include: EntryPointFileFullPath);
+        projectRoot.FullPath = projectFileFullPath;
+        return projectRoot;
+    }
+}
diff --git a/src/Cli/dotnet/commands/dotnet-run/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-run/LocalizableStrings.resx
index 7835c0542477..79325816a555 100644
--- a/src/Cli/dotnet/commands/dotnet-run/LocalizableStrings.resx
+++ b/src/Cli/dotnet/commands/dotnet-run/LocalizableStrings.resx
@@ -244,4 +244,7 @@ Make the profile names distinct.</value>
   <data name="LaunchProfileDoesNotExist" xml:space="preserve">
     <value>A launch profile with the name '{0}' doesn't exist.</value>
   </data>
+  <data name="NoTopLevelStatements" xml:space="preserve">
+    <value>Cannot run a file without top-level statements and without a project: '{0}'</value>
+  </data>
 </root>
diff --git a/src/Cli/dotnet/commands/dotnet-run/Program.cs b/src/Cli/dotnet/commands/dotnet-run/Program.cs
index 0b7d76ec648f..e6dbf2f0225d 100644
--- a/src/Cli/dotnet/commands/dotnet-run/Program.cs
+++ b/src/Cli/dotnet/commands/dotnet-run/Program.cs
@@ -19,6 +19,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.OrdinalIgnoreCase;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
+
         public static RunCommand FromParseResult(ParseResult parseResult)
         {
             if (parseResult.UsingRunCommandShorthandProjectOption())
@@ -37,10 +45,7 @@ public static RunCommand FromParseResult(ParseResult parseResult)
             var nonBinLogArgs = new List<string>();
             foreach (var arg in applicationArguments)
             {
-
-                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
-                    || arg.StartsWith("--binaryLogger:") || arg.Equals("--binaryLogger")
-                    || arg.StartsWith("-bl:") || arg.Equals("-bl"))
+                if (IsBinLogArgument(arg))
                 {
                     binlogArgs.Add(arg);
                 }
diff --git a/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs b/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
index d596c6a60714..0a237a80caa7 100644
--- a/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
+++ b/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
@@ -1,12 +1,17 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Reflection;
+#nullable enable
+
+using System.Diagnostics;
 using Microsoft.Build.Evaluation;
 using Microsoft.Build.Exceptions;
 using Microsoft.Build.Execution;
 using Microsoft.Build.Framework;
 using Microsoft.Build.Logging;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
 using Microsoft.DotNet.Cli;
 using Microsoft.DotNet.Cli.Extensions;
 using Microsoft.DotNet.Cli.Utils;
@@ -21,8 +26,24 @@ public partial class RunCommand
         private record RunProperties(string? RunCommand, string? RunArguments, string? RunWorkingDirectory);
 
         public bool NoBuild { get; }
+
+        /// <summary>
+        /// Value of the <c>--project</c> option.
+        /// </summary>
         public string? ProjectFileOrDirectory { get; }
-        public string ProjectFileFullPath { get; }
+
+        /// <summary>
+        /// Full path to a project file to run.
+        /// <see langword="null"/> if running without a project file
+        /// (then <see cref="EntryPointFileFullPath"/> is not <see langword="null"/>).
+        /// </summary>
+        public string? ProjectFileFullPath { get; }
+
+        /// <summary>
+        /// Full path to an entry-point <c>.cs</c> file to run without a project file.
+        /// </summary>
+        public string? EntryPointFileFullPath { get; }
+
         public string[] Args { get; set; }
         public bool NoRestore { get; }
         public VerbosityOptions? Verbosity { get; }
@@ -59,7 +80,8 @@ public RunCommand(
         {
             NoBuild = noBuild;
             ProjectFileOrDirectory = projectFileOrDirectory;
-            ProjectFileFullPath = DiscoverProjectFilePath(projectFileOrDirectory);
+            ProjectFileFullPath = DiscoverProjectFilePath(projectFileOrDirectory, ref args, out string? entryPointFileFullPath);
+            EntryPointFileFullPath = entryPointFileFullPath;
             LaunchProfile = launchProfile;
             NoLaunchProfile = noLaunchProfile;
             NoLaunchProfileArguments = noLaunchProfileArguments;
@@ -78,6 +100,7 @@ public int Execute()
                 return 1;
             }
 
+            Func<ProjectCollection, ProjectInstance>? projectFactory = null;
             if (ShouldBuild)
             {
                 if (string.Equals("true", launchSettings?.DotNetRunMessages, StringComparison.OrdinalIgnoreCase))
@@ -85,12 +108,19 @@ public int Execute()
                     Reporter.Output.WriteLine(LocalizableStrings.RunCommandBuilding);
                 }
 
-                EnsureProjectIsBuilt();
+                EnsureProjectIsBuilt(out projectFactory);
+            }
+            else if (EntryPointFileFullPath is not null)
+            {
+                projectFactory = new VirtualProjectBuildingCommand
+                {
+                    EntryPointFileFullPath = EntryPointFileFullPath,
+                }.CreateProjectInstance;
             }
 
             try
             {
-                ICommand targetCommand = GetTargetCommand();
+                ICommand targetCommand = GetTargetCommand(projectFactory);
                 ApplyLaunchSettingsProfileToCommand(targetCommand, launchSettings);
 
                 // Env variables specified on command line override those specified in launch profile:
@@ -147,7 +177,7 @@ private bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel?
                 return true;
             }
 
-            var launchSettingsPath = TryFindLaunchSettings(ProjectFileFullPath);
+            var launchSettingsPath = TryFindLaunchSettings(ProjectFileFullPath ?? EntryPointFileFullPath!);
             if (!File.Exists(launchSettingsPath))
             {
                 if (!string.IsNullOrEmpty(LaunchProfile))
@@ -186,9 +216,9 @@ private bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel?
 
             return true;
 
-            static string? TryFindLaunchSettings(string projectFilePath)
+            static string? TryFindLaunchSettings(string projectOrEntryPointFilePath)
             {
-                var buildPathContainer = File.Exists(projectFilePath) ? Path.GetDirectoryName(projectFilePath) : projectFilePath;
+                var buildPathContainer = File.Exists(projectOrEntryPointFilePath) ? Path.GetDirectoryName(projectOrEntryPointFilePath) : projectOrEntryPointFilePath;
                 if (buildPathContainer is null)
                 {
                     return null;
@@ -199,7 +229,7 @@ private bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel?
                 // VB.NET projects store the launch settings file in the
                 // "My Project" directory instead of a "Properties" directory.
                 // TODO: use the `AppDesignerFolder` MSBuild property instead, which captures this logic already
-                if (string.Equals(Path.GetExtension(projectFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(Path.GetExtension(projectOrEntryPointFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase))
                 {
                     propsDirectory = "My Project";
                 }
@@ -213,14 +243,34 @@ private bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel?
             }
         }
 
-        private void EnsureProjectIsBuilt()
+        private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory)
         {
-            var buildResult =
-                new RestoringCommand(
+            int buildResult;
+            if (EntryPointFileFullPath is not null)
+            {
+                var command = new VirtualProjectBuildingCommand
+                {
+                    EntryPointFileFullPath = EntryPointFileFullPath,
+                };
+
+                AddUserPassedProperties(command.GlobalProperties, RestoreArgs);
+
+                projectFactory = command.CreateProjectInstance;
+                buildResult = command.Execute(
+                    binaryLoggerArgs: RestoreArgs,
+                    consoleLogger: MakeTerminalLogger(Verbosity ?? GetDefaultVerbosity()));
+            }
+            else
+            {
+                Debug.Assert(ProjectFileFullPath is not null);
+
+                projectFactory = null;
+                buildResult = new RestoringCommand(
                     RestoreArgs.Prepend(ProjectFileFullPath),
                     NoRestore,
                     advertiseWorkloadUpdates: false
                 ).Execute();
+            }
 
             if (buildResult != 0)
             {
@@ -236,12 +286,9 @@ private string[] GetRestoreArguments(IEnumerable<string> cliRestoreArgs)
                 "-nologo"
             };
 
-            // --interactive need to output guide for auth. It cannot be
-            // completely "quiet"
             if (Verbosity is null)
             {
-                var defaultVerbosity = Interactive ? "minimal" : "quiet";
-                args.Add($"-verbosity:{defaultVerbosity}");
+                args.Add($"-verbosity:{GetDefaultVerbosity()}");
             }
 
             args.AddRange(cliRestoreArgs);
@@ -249,10 +296,17 @@ private string[] GetRestoreArguments(IEnumerable<string> cliRestoreArgs)
             return args.ToArray();
         }
 
-        private ICommand GetTargetCommand()
+        private VerbosityOptions GetDefaultVerbosity()
+        {
+            // --interactive need to output guide for auth. It cannot be
+            // completely "quiet"
+            return Interactive ? VerbosityOptions.minimal : VerbosityOptions.quiet;
+        }
+
+        private ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? projectFactory)
         {
             FacadeLogger? logger = DetermineBinlogger(RestoreArgs);
-            var project = EvaluateProject(ProjectFileFullPath, RestoreArgs, logger);
+            var project = EvaluateProject(ProjectFileFullPath, projectFactory, RestoreArgs, logger);
             ValidatePreconditions(project);
             InvokeRunArgumentsTarget(project, RestoreArgs, Verbosity, logger);
             logger?.ReallyShutdown();
@@ -260,8 +314,10 @@ private ICommand GetTargetCommand()
             var command = CreateCommandFromRunProperties(project, runProperties);
             return command;
 
-            static ProjectInstance EvaluateProject(string projectFilePath, string[] restoreArgs, ILogger? binaryLogger)
+            static ProjectInstance EvaluateProject(string? projectFilePath, Func<ProjectCollection, ProjectInstance>? projectFactory, string[] restoreArgs, ILogger? binaryLogger)
             {
+                Debug.Assert(projectFilePath is not null || projectFactory is not null);
+
                 var globalProperties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                 {
                     // This property disables default item globbing to improve performance
@@ -270,16 +326,17 @@ static ProjectInstance EvaluateProject(string projectFilePath, string[] restoreA
                     { Constants.MSBuildExtensionsPath, AppContext.BaseDirectory }
                 };
 
-                var userPassedProperties = DeriveUserPassedProperties(restoreArgs);
-                if (userPassedProperties is not null)
+                AddUserPassedProperties(globalProperties, restoreArgs);
+
+                var collection = new ProjectCollection(globalProperties: globalProperties, loggers: binaryLogger is null ? null : [binaryLogger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
+
+                if (projectFilePath is not null)
                 {
-                    foreach (var (key, values) in userPassedProperties)
-                    {
-                        globalProperties[key] = string.Join(";", values);
-                    }
+                    return collection.LoadProject(projectFilePath).CreateProjectInstance();
                 }
-                var collection = new ProjectCollection(globalProperties: globalProperties, loggers: binaryLogger is null ? null : [binaryLogger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);
-                return collection.LoadProject(projectFilePath).CreateProjectInstance();
+
+                Debug.Assert(projectFactory is not null);
+                return projectFactory(collection);
             }
 
             static void ValidatePreconditions(ProjectInstance project)
@@ -290,35 +347,6 @@ static void ValidatePreconditions(ProjectInstance project)
                 }
             }
 
-            static Dictionary<string, List<string>>? DeriveUserPassedProperties(string[] args)
-            {
-                var fakeCommand = new System.CommandLine.CliCommand("dotnet") { CommonOptions.PropertiesOption };
-                var propertyParsingConfiguration = new System.CommandLine.CliConfiguration(fakeCommand);
-                var propertyParseResult = propertyParsingConfiguration.Parse(args);
-                var propertyValues = propertyParseResult.GetValue(CommonOptions.PropertiesOption);
-
-                if (propertyValues != null)
-                {
-                    var userPassedProperties = new Dictionary<string, List<string>>(propertyValues.Length, StringComparer.OrdinalIgnoreCase);
-                    foreach (var property in propertyValues)
-                    {
-                        foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property))
-                        {
-                            if (userPassedProperties.TryGetValue(key, out var existingValues))
-                            {
-                                existingValues.Add(value);
-                            }
-                            else
-                            {
-                                userPassedProperties[key] = [value];
-                            }
-                        }
-                    }
-                    return userPassedProperties;
-                }
-                return null;
-            }
-
             static RunProperties ReadRunPropertiesFromProject(ProjectInstance project, string[] applicationArgs)
             {
                 string runProgram = project.GetPropertyValue("RunCommand");
@@ -375,11 +403,16 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, string[] restoreAr
 
             static FacadeLogger? DetermineBinlogger(string[] restoreArgs)
             {
-
                 List<BinaryLogger> binaryLoggers = new();
 
-                foreach (var blArg in restoreArgs.Where(arg => arg.StartsWith("-bl", StringComparison.OrdinalIgnoreCase)))
+                for (int i = restoreArgs.Length - 1; i >= 0; i--)
                 {
+                    string blArg = restoreArgs[i];
+                    if (!IsBinLogArgument(blArg))
+                    {
+                        continue;
+                    }
+
                     if (blArg.Contains(':'))
                     {
                         // split and forward args
@@ -398,6 +431,9 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, string[] restoreAr
                         var filename = "msbuild-dotnet-run" + ".binlog";
                         binaryLoggers.Add(new BinaryLogger { Parameters = filename });
                     }
+
+                    // Like in MSBuild, only the last binary logger is used.
+                    break;
                 }
 
                 // this binaryLogger needs to be used for both evaluation and execution, so we need to only call it with a single IEventSource across
@@ -419,6 +455,37 @@ static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
             }
         }
 
+        /// <param name="globalProperties">
+        /// Should have <see cref="StringComparer.OrdinalIgnoreCase"/>.
+        /// </param>
+        private static void AddUserPassedProperties(Dictionary<string, string> globalProperties, string[] args)
+        {
+            Debug.Assert(globalProperties.Comparer == StringComparer.OrdinalIgnoreCase);
+
+            var fakeCommand = new System.CommandLine.CliCommand("dotnet") { CommonOptions.PropertiesOption };
+            var propertyParsingConfiguration = new System.CommandLine.CliConfiguration(fakeCommand);
+            var propertyParseResult = propertyParsingConfiguration.Parse(args);
+            var propertyValues = propertyParseResult.GetValue(CommonOptions.PropertiesOption);
+
+            if (propertyValues != null)
+            {
+                foreach (var property in propertyValues)
+                {
+                    foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property))
+                    {
+                        if (globalProperties.TryGetValue(key, out var existingValues))
+                        {
+                            globalProperties[key] = existingValues + ";" + value;
+                        }
+                        else
+                        {
+                            globalProperties[key] = value;
+                        }
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// This class acts as a wrapper around the BinaryLogger, to allow us to keep the BinaryLogger alive across multiple phases of the build.
         /// The methods here are stubs so that the real binarylogger sees that we support these functionalities.
@@ -547,34 +614,78 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
 
-            if (projectFiles.Length == 0)
+            static string? TryFindSingleProjectInDirectory(string directory)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
             }
-            else if (projectFiles.Length > 1)
+
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                if (args is not [{ } arg, ..] ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
+                {
+                    throw new GracefulException(LocalizableStrings.NoTopLevelStatements, arg);
+                }
+
+                args = args[1..];
+                return Path.GetFullPath(arg);
             }
 
-            return projectFiles[0];
+            static bool HasTopLevelStatements(string entryPointFilePath)
+            {
+                var tree = ParseCSharp(entryPointFilePath);
+                return tree.GetRoot().ChildNodes().OfType<GlobalStatementSyntax>().Any();
+            }
+
+            static CSharpSyntaxTree ParseCSharp(string filePath)
+            {
+                using var stream = File.OpenRead(filePath);
+                return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(SourceText.From(stream, Encoding.UTF8), path: filePath);
+            }
         }
     }
 }
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.cs.xlf
index 59ada52ff3af..abfc7d427c45 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.cs.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.cs.xlf
@@ -51,6 +51,11 @@ Nastavte odlišné názvy profilů.</target>
         <target state="translated">Profil spuštění s názvem {0} neexistuje.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Pomocí možnosti -p je možné v jednu chvíli zadat pouze jeden projekt.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.de.xlf
index ecec47ebfcae..0536b262adc6 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.de.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.de.xlf
@@ -51,6 +51,11 @@ Erstellen Sie eindeutige Profilnamen.</target>
         <target state="translated">Es ist kein Startprofil mit dem Namen "{0}" vorhanden.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Nur jeweils ein Projekt kann mithilfe der Option „-p“ angegeben werden.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.es.xlf
index 63695e47e069..6493f1225514 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.es.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.es.xlf
@@ -51,6 +51,11 @@ Defina nombres de perfiles distintos.</target>
         <target state="translated">No existe ningún perfil de inicio con el nombre "{0}".</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Solo se puede especificar un proyecto a la vez mediante la opción -p.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.fr.xlf
index 286a7a541634..ba6804b350aa 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.fr.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.fr.xlf
@@ -51,6 +51,11 @@ faites en sorte que les noms de profil soient distincts.</target>
         <target state="translated">Un profil de lancement avec le nom '{0}' n'existe pas.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Vous ne pouvez spécifier qu’un seul projet à la fois à l’aide de l’option -p.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.it.xlf
index 5f52c5e48fae..824c4eec596d 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.it.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.it.xlf
@@ -51,6 +51,11 @@ Rendi distinti i nomi dei profili.</target>
         <target state="translated">Non esiste un profilo di avvio con il nome '{0}'.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">È possibile specificare un solo progetto alla volta utilizzando l'opzione -p.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ja.xlf
index ab29ec8d15c3..2b85677d654a 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ja.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ja.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">'{0} ' という名前の起動プロファイルは存在しません。</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">-p オプションを使用して一度に指定できるプロジェクトは 1 つだけです。</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ko.xlf
index a2b77c1bc8bf..f6763e33f8a2 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ko.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ko.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">이름이 '{0}'인 시작 프로필이 없습니다.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">-p 옵션을 사용하여 한 번에 하나의 프로젝트만 지정할 수 있습니다.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pl.xlf
index d013b5a9a62f..b2caf7058159 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pl.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pl.xlf
@@ -51,6 +51,11 @@ Rozróżnij nazwy profilów.</target>
         <target state="translated">Profil uruchamiania o nazwie „{0}” nie istnieje.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Jednocześnie można określić tylko jeden projekt przy użyciu opcji -p.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pt-BR.xlf
index 258d2670d7e7..cf2710bf8fe8 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pt-BR.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.pt-BR.xlf
@@ -51,6 +51,11 @@ Diferencie os nomes dos perfis.</target>
         <target state="translated">Um perfil de lançamento com o nome '{0}' não existe.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">Somente um projeto pode ser especificado por vez usando a opção -p.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ru.xlf
index 78de4b5ebcd1..54cdd26340fb 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ru.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.ru.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">Профиль запуска с именем "{0}" не существует.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">С помощью параметра -p можно указать только один проект за раз.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.tr.xlf
index 167786e6277a..6ee59dd32733 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.tr.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.tr.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">'{0}' adlı bir başlatma profili yok.</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">-p seçeneği kullanılarak tek seferde yalnızca bir proje belirtilebilir.</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hans.xlf
index 799e30f616a5..0f404a00327a 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hans.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hans.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">名为“{0}”的启动配置文件不存在。</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">使用 -p 选项时一次只能指定一个项目。</target>
diff --git a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hant.xlf
index cec07d6b8f89..47feeab54b82 100644
--- a/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hant.xlf
+++ b/src/Cli/dotnet/commands/dotnet-run/xlf/LocalizableStrings.zh-Hant.xlf
@@ -51,6 +51,11 @@ Make the profile names distinct.</source>
         <target state="translated">名稱為 '{0}' 的啟動設定檔不存在。</target>
         <note />
       </trans-unit>
+      <trans-unit id="NoTopLevelStatements">
+        <source>Cannot run a file without top-level statements and without a project: '{0}'</source>
+        <target state="new">Cannot run a file without top-level statements and without a project: '{0}'</target>
+        <note />
+      </trans-unit>
       <trans-unit id="OnlyOneProjectAllowed">
         <source>Only one project can be specified at a time using the -p option.</source>
         <target state="translated">使用 -p 選項時，一次只能指定一個專案。</target>
diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj
index 7cdb351f6768..8f5cf52e0d3d 100644
--- a/src/Cli/dotnet/dotnet.csproj
+++ b/src/Cli/dotnet/dotnet.csproj
@@ -115,6 +115,7 @@
     <!-- Lift dependency of NETStandard.Library.NETFramework to version produced in SBRP. -->
     <PackageReference Include="NETStandard.Library" VersionOverride="$(NETStandardLibraryVersion)" ExcludeAssets="All" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Ini" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
   </ItemGroup>
   <ItemGroup Condition=" '$(IncludeAspNetCoreRuntime)' != 'false' ">
     <PackageReference Include="Microsoft.AspNetCore.DeveloperCertificates.XPlat" />
diff --git a/test/dotnet-run.Tests/RunFileTests.cs b/test/dotnet-run.Tests/RunFileTests.cs
new file mode 100644
index 000000000000..79c4e85222cc
--- /dev/null
+++ b/test/dotnet-run.Tests/RunFileTests.cs
@@ -0,0 +1,747 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Cli.Run.Tests;
+
+public sealed class RunFileTests(ITestOutputHelper log) : SdkTest(log)
+{
+    private static readonly string s_program = """
+        if (args.Length > 0)
+        {
+            Console.WriteLine("echo args:" + string.Join(";", args));
+        }
+        Console.WriteLine("Hello from " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
+        #if !DEBUG
+        Console.WriteLine("Release config");
+        #endif
+        """;
+
+    private static readonly string s_programDependingOnUtil = """
+        if (args.Length > 0)
+        {
+            Console.WriteLine("echo args:" + string.Join(";", args));
+        }
+        Console.WriteLine("Hello, " + Util.GetMessage());
+        """;
+
+    private static readonly string s_util = """
+        static class Util
+        {
+            public static string GetMessage()
+            {
+                return "String from Util";
+            }
+        }
+        """;
+
+    private static readonly string s_consoleProject = $"""
+        <Project Sdk="Microsoft.NET.Sdk">
+          <PropertyGroup>
+            <OutputType>Exe</OutputType>
+            <TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
+            <ImplicitUsings>enable</ImplicitUsings>
+          </PropertyGroup>
+        </Project>
+        """;
+
+    private static readonly string s_launchSettings = """
+        {
+            "profiles": {
+                "TestProfile1": {
+                    "commandName": "Project",
+                    "environmentVariables": {
+                        "Message": "TestProfileMessage1"
+                    }
+                },
+                "TestProfile2": {
+                    "commandName": "Project",
+                    "environmentVariables": {
+                        "Message": "TestProfileMessage2"
+                    }
+                }
+            }
+        }
+        """;
+
+    private static readonly string s_runCommandExceptionNoProjects =
+        "Couldn't find a project to run.";
+
+    private static readonly string s_noTopLevelStatements =
+        "Cannot run a file without top-level statements and without a project:";
+
+    private static bool HasCaseInsensitiveFileSystem
+    {
+        get
+        {
+            return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+                || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+        }
+    }
+
+    /// <summary>
+    /// <c>dotnet run file.cs</c> succeeds without a project file.
+    /// </summary>
+    [Theory]
+    [InlineData(null, false)] // will be replaced with an absolute path
+    [InlineData("Program.cs", false)]
+    [InlineData("./Program.cs", false)]
+    [InlineData("Program.CS", true)]
+    public void FilePath(string? path, bool differentCasing)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+
+        var programPath = Path.Join(testInstance.Path, "Program.cs");
+
+        File.WriteAllText(programPath, s_program);
+
+        path ??= programPath;
+
+        var result = new DotnetCommand(Log, "run", path)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute();
+
+        if (!differentCasing || HasCaseInsensitiveFileSystem)
+        {
+            result.Should().Pass()
+                .And.HaveStdOut("Hello from Program");
+        }
+        else
+        {
+            result.Should().Fail()
+                .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+        }
+    }
+
+    /// <summary>
+    /// Casing of the argument is used for the output binary name.
+    /// </summary>
+    [Fact]
+    public void FilePath_DifferentCasing()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        var result = new DotnetCommand(Log, "run", "program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute();
+
+        if (HasCaseInsensitiveFileSystem)
+        {
+            result.Should().Pass()
+                .And.HaveStdOut("Hello from program");
+        }
+        else
+        {
+            result.Should().Fail()
+                .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+        }
+    }
+
+    /// <summary>
+    /// <c>dotnet run folder/file.cs</c> succeeds without a project file.
+    /// </summary>
+    [Fact]
+    public void FilePath_OutsideWorkDir()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        var dirName = Path.GetFileName(testInstance.Path);
+
+        new DotnetCommand(Log, "run", $"{dirName}/Program.cs")
+            .WithWorkingDirectory(Path.GetDirectoryName(testInstance.Path)!)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+    }
+
+    /// <summary>
+    /// <c>dotnet run --project file.cs</c> fails.
+    /// </summary>
+    [Fact]
+    public void FilePath_AsProjectArgument()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "--project", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(LocalizableStrings.RunCommandException);
+    }
+
+    /// <summary>
+    /// <c>dotnet run folder</c> without a project file is not supported.
+    /// </summary>
+    [Theory]
+    [InlineData(null)] // will be replaced with an absolute path
+    [InlineData(".")]
+    [InlineData("../MSBuildTestApp")]
+    [InlineData("../MSBuildTestApp/")]
+    public void FolderPath(string? path)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        path ??= testInstance.Path;
+
+        new DotnetCommand(Log, "run", path)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// <c>dotnet run app.csproj</c> fails if app.csproj does not exist.
+    /// </summary>
+    [Fact]
+    public void ProjectPath_DoesNotExist()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "./App.csproj")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// <c>dotnet run app.csproj</c> where app.csproj exists
+    /// runs the project and passes 'app.csproj' as an argument.
+    /// </summary>
+    [Fact]
+    public void ProjectPath_Exists()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+        File.WriteAllText(Path.Join(testInstance.Path, "App.csproj"), s_consoleProject);
+
+        new DotnetCommand(Log, "run", "./App.csproj")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOutContaining("""
+                echo args:./App.csproj
+                Hello from App
+                """);
+    }
+
+    /// <summary>
+    /// Only <c>.cs</c> files can be run without a project file,
+    /// others fall back to normal <c>dotnet run</c> behavior.
+    /// </summary>
+    [Theory]
+    [InlineData("Program")]
+    [InlineData("Program.csx")]
+    [InlineData("Program.vb")]
+    public void NonCsFileExtension(string fileName)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, fileName), s_program);
+
+        new DotnetCommand(Log, "run", fileName)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    [Fact]
+    public void MultipleEntryPoints()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+        File.WriteAllText(Path.Join(testInstance.Path, "Program2.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        new DotnetCommand(Log, "run", "Program2.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program2");
+    }
+
+    /// <summary>
+    /// When the entry-point file does not exist, fallback to normal <c>dotnet run</c> behavior.
+    /// </summary>
+    [Fact]
+    public void NoCode()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// Cannot run a non-entry-point file.
+    /// </summary>
+    [Fact]
+    public void ClassLibrary_EntryPointFileExists()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), s_util);
+
+        new DotnetCommand(Log, "run", "Util.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_noTopLevelStatements);
+    }
+
+    /// <summary>
+    /// When the entry-point file does not exist, fallback to normal <c>dotnet run</c> behavior.
+    /// </summary>
+    [Fact]
+    public void ClassLibrary_EntryPointFileDoesNotExist()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), s_util);
+
+        new DotnetCommand(Log, "run", "NonExistentFile.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// Other files in the folder are not part of the compilation.
+    /// </summary>
+    [Fact]
+    public void MultipleFiles_RunEntryPoint()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programDependingOnUtil);
+        File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), s_util);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdOutContaining("error CS0103"); // The name 'Util' does not exist in the current context
+    }
+
+    /// <summary>
+    /// <c>dotnet run util.cs</c> fails if <c>util.cs</c> is not the entry-point.
+    /// </summary>
+    [Fact]
+    public void MultipleFiles_RunLibraryFile()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programDependingOnUtil);
+        File.WriteAllText(Path.Join(testInstance.Path, "Util.cs"), s_util);
+
+        new DotnetCommand(Log, "run", "Util.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_noTopLevelStatements);
+    }
+
+    /// <summary>
+    /// If there are nested project files like
+    /// <code>
+    ///  app/file.cs
+    ///  app/nested/x.csproj
+    ///  app/nested/another.cs
+    /// </code>
+    /// executing <c>dotnet run app/file.cs</c> will include the nested <c>.cs</c> file in the compilation.
+    /// Hence we could consider reporting an error in this situation.
+    /// However, the same problem exists for normal builds with explicit project files
+    /// and usually the build fails because there are multiple entry points or other clashes.
+    /// </summary>
+    [Fact]
+    public void NestedProjectFiles()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+        Directory.CreateDirectory(Path.Join(testInstance.Path, "nested"));
+        File.WriteAllText(Path.Join(testInstance.Path, "nested", "App.csproj"), s_consoleProject);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+    }
+
+    /// <summary>
+    /// <c>dotnet run folder/app.csproj</c> -> the argument is not recognized as an entry-point file
+    /// (it does not have <c>.cs</c> file extension), so this fallbacks to normal <c>dotnet run</c> behavior.
+    /// </summary>
+    [Fact]
+    public void RunNestedProjectFile()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+        File.WriteAllText(Path.Join(testInstance.Path, "App.csproj"), s_consoleProject);
+
+        var dirName = Path.GetFileName(testInstance.Path);
+
+        new DotnetCommand(Log, "run", $"{dirName}/App.csproj")
+            .WithWorkingDirectory(Path.GetDirectoryName(testInstance.Path)!)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// Only top-level statements are supported for now; Main method is not.
+    /// </summary>
+    [Fact]
+    public void MainMethod()
+    {
+        var testInstance = _testAssetsManager.CopyTestAsset("MSBuildTestApp").WithSource();
+        File.Delete(Path.Join(testInstance.Path, "MSBuildTestApp.csproj"));
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_noTopLevelStatements);
+    }
+
+    /// <summary>
+    /// Empty file does not contain top-level statements, so that's an error.
+    /// </summary>
+    [Fact]
+    public void EmptyFile()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), string.Empty);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_noTopLevelStatements);
+    }
+
+    /// <summary>
+    /// Implicit build files have an effect.
+    /// </summary>
+    [Fact]
+    public void DirectoryBuildProps()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+        File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+            <Project>
+                <PropertyGroup>
+                    <AssemblyName>TestName</AssemblyName>
+                </PropertyGroup>
+            </Project>
+            """);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from TestName");
+    }
+
+    /// <summary>
+    /// Command-line arguments should be passed through.
+    /// </summary>
+    [Theory]
+    [InlineData("other;args", "other;args")]
+    [InlineData("--;other;args", "other;args")]
+    [InlineData("--appArg", "--appArg")]
+    [InlineData("-c;Debug;--xyz", "--xyz")]
+    public void Arguments_PassThrough(string input, string output)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, ["run", "Program.cs", .. input.Split(';')])
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut($"""
+                echo args:{output}
+                Hello from Program
+                """);
+    }
+
+    /// <summary>
+    /// <c>dotnet run --unknown-arg file.cs</c> fallbacks to normal <c>dotnet run</c> behavior.
+    /// </summary>
+    [Fact]
+    public void Arguments_Unrecognized()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, ["run", "--arg", "Program.cs"])
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining(s_runCommandExceptionNoProjects);
+    }
+
+    /// <summary>
+    /// <c>dotnet run --some-known-arg file.cs</c> is supported.
+    /// </summary>
+    [Theory, CombinatorialData]
+    public void Arguments_Recognized(bool beforeFile)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        string[] args = beforeFile
+            ? ["run", "-c", "Release", "Program.cs", "more", "args"]
+            : ["run", "Program.cs", "-c", "Release", "more", "args"];
+
+        new DotnetCommand(Log, args)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("""
+                echo args:more;args
+                Hello from Program
+                Release config
+                """);
+    }
+
+    /// <summary>
+    /// <c>dotnet run --bl file.cs</c> produces a binary log.
+    /// </summary>
+    [Theory, CombinatorialData]
+    public void BinaryLog(bool beforeFile)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        string[] args = beforeFile
+            ? ["run", "-bl", "Program.cs"]
+            : ["run", "Program.cs", "-bl"];
+
+        new DotnetCommand(Log, args)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        new DirectoryInfo(testInstance.Path)
+            .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly)
+            .Select(f => f.Name)
+            .Should().BeEquivalentTo(["msbuild.binlog", "msbuild-dotnet-run.binlog"]);
+    }
+
+    [Theory]
+    [InlineData("-bl")]
+    [InlineData("-BL")]
+    [InlineData("-bl:msbuild.binlog")]
+    [InlineData("/bl")]
+    [InlineData("/bl:msbuild.binlog")]
+    [InlineData("--binaryLogger")]
+    [InlineData("--binaryLogger:msbuild.binlog")]
+    [InlineData("-bl:another.binlog")]
+    public void BinaryLog_ArgumentForms(string arg)
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "Program.cs", arg)
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        var fileName = arg.Split(':', 2) is [_, { Length: > 0 } value] ? Path.GetFileNameWithoutExtension(value) : "msbuild";
+
+        new DirectoryInfo(testInstance.Path)
+            .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly)
+            .Select(f => f.Name)
+            .Should().BeEquivalentTo([$"{fileName}.binlog", $"{fileName}-dotnet-run.binlog"]);
+    }
+
+    [Fact]
+    public void BinaryLog_Multiple()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "Program.cs", "-bl:one.binlog", "two.binlog", "/bl:three.binlog")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("""
+                echo args:two.binlog
+                Hello from Program
+                """);
+
+        new DirectoryInfo(testInstance.Path)
+            .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly)
+            .Select(f => f.Name)
+            .Should().BeEquivalentTo(["three.binlog", "three-dotnet-run.binlog"]);
+    }
+
+    [Fact]
+    public void BinaryLog_WrongExtension()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "Program.cs", "-bl:test.test")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining("""
+                Invalid binary logger parameter(s): "test.test"
+                """);
+
+        new DirectoryInfo(testInstance.Path)
+            .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly)
+            .Select(f => f.Name)
+            .Should().BeEmpty();
+    }
+
+    /// <summary>
+    /// <c>dotnet run file.cs</c> should not produce a binary log.
+    /// </summary>
+    [Fact]
+    public void BinaryLog_NotSpecified()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        new DirectoryInfo(testInstance.Path)
+            .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly)
+            .Select(f => f.Name)
+            .Should().BeEmpty();
+    }
+
+    /// <summary>
+    /// Default projects do not include anything apart from the entry-point file.
+    /// </summary>
+    [Fact]
+    public void EmbeddedResource()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+            using var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Program.Resources.resources");
+
+            if (stream is null)
+            {
+                Console.WriteLine("Resource not found");
+                return;
+            }
+
+            using var reader = new System.Resources.ResourceReader(stream);
+            Console.WriteLine(reader.Cast<System.Collections.DictionaryEntry>().Single());
+            """);
+        File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), """
+            <root>
+              <data name="MyString">
+                <value>TestValue</value>
+              </data>
+            </root>
+            """);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("""
+                Resource not found
+                """);
+    }
+
+    [Fact]
+    public void NoBuild()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        var programFile = Path.Join(testInstance.Path, "Program.cs");
+        File.WriteAllText(programFile, s_program);
+
+        // It is an error when never built before.
+        new DotnetCommand(Log, "run", "--no-build", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Fail()
+            .And.HaveStdErrContaining("An error occurred trying to start process");
+
+        // Now build it.
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        // Changing the program has no effect when it is not built.
+        File.WriteAllText(programFile, """Console.WriteLine("Changed");""");
+        new DotnetCommand(Log, "run", "--no-build", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Hello from Program");
+
+        // The change has an effect when built again.
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("Changed");
+    }
+
+    [Fact]
+    public void LaunchProfile()
+    {
+        var testInstance = _testAssetsManager.CreateTestDirectory();
+        File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program + """
+
+            Console.WriteLine($"Message: '{Environment.GetEnvironmentVariable("Message")}'");
+            """);
+        Directory.CreateDirectory(Path.Join(testInstance.Path, "Properties"));
+        File.WriteAllText(Path.Join(testInstance.Path, "Properties", "launchSettings.json"), s_launchSettings);
+
+        new DotnetCommand(Log, "run", "--no-launch-profile", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOut("""
+                Hello from Program
+                Message: ''
+                """);
+
+        new DotnetCommand(Log, "run", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOutContaining("""
+                Hello from Program
+                Message: 'TestProfileMessage1'
+                """);
+
+        new DotnetCommand(Log, "run", "-lp", "TestProfile2", "Program.cs")
+            .WithWorkingDirectory(testInstance.Path)
+            .Execute()
+            .Should().Pass()
+            .And.HaveStdOutContaining("""
+                Hello from Program
+                Message: 'TestProfileMessage2'
+                """);
+    }
+}
diff --git a/test/dotnet.Tests/dotnet.Tests.csproj b/test/dotnet.Tests/dotnet.Tests.csproj
index e22cdf7dfa23..7731ac8d9ad7 100644
--- a/test/dotnet.Tests/dotnet.Tests.csproj
+++ b/test/dotnet.Tests/dotnet.Tests.csproj
@@ -126,6 +126,7 @@
     <PackageReference Include="Verify.DiffPlex" />
     <!-- This package comes from dotnet/runtime-assets -->
     <PackageReference Include="Microsoft.DotNet.Installer.Windows.Security.TestData" GeneratePathProperty="true" />
+    <PackageReference Include="Xunit.Combinatorial" />
   </ItemGroup>
 
   <!-- Global usings -->


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/Cli/dotnet/commands/dotnet-run/RunCommand.cs:609**
* [nitpick] The method 'DiscoverProjectFilePath' now returns both a project file path and an entry point file via an out parameter; consider renaming it (for example, to 'DiscoverProjectAndEntryPointPaths') to better reflect its dual purpose.
```
private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
```
**src/Cli/dotnet/commands/dotnet-run/RunCommand.cs:250**
* [nitpick] Consider renaming the 'projectFactory' out parameter to 'projectInstanceFactory' for improved clarity since it is intended to supply a ProjectInstance.
```
private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory)
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T12:16:52Z
user: login: jaredpar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-19T22:46:05Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T08:41:37Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T08:44:23Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T08:48:22Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T08:55:43Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T08:58:41Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T11:46:52Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:35:39Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:38:48Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:40:32Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:43:49Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:49:32Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T16:56:11Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T17:01:06Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T17:03:31Z
user: login: RikkiGibson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T01:41:05Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T08:19:50Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T08:21:14Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T08:25:52Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T08:29:06Z
user: login: RikkiGibson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T14:33:45Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:24:38Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:25:54Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:28:20Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:48:51Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:49:05Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T17:34:50Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T17:34:58Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T18:02:56Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T18:12:37Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T18:22:13Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T18:26:23Z
user: login: jeffkl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T18:48:59Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T15:09:00Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T15:15:53Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T17:06:31Z
user: login: baronfel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T17:10:45Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T17:14:15Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T17:26:08Z
user: login: hez2010
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-04T01:36:41Z
user: login: jaredpar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T07:13:42Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T09:17:27Z
user: login: RikkiGibson
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-04T17:58:27Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:01:54Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T22:47:23Z
user: login: Forgind
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T23:33:17Z
user: login: DamianEdwards
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T00:15:19Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T00:57:15Z
user: login: MiYanni
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-05T01:41:20Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:10:04Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:12:31Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:14:40Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:19:52Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:28:18Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:30:31Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:31:35Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:32:42Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:37:58Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:46:56Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T10:25:30Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T10:37:41Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T11:03:07Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T17:31:32Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T17:58:38Z
user: login: nkolev92
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T18:00:24Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T18:04:19Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T18:09:17Z
user: login: DamianEdwards
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T20:43:52Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:23:33Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:45:29Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T01:03:05Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T01:08:41Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T01:09:26Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T06:48:47Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T06:51:09Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T06:53:09Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T06:59:14Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:15:30Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:28:54Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:45:29Z
user: login: Forgind
body: I heard offline that the new plan is to have it just build/execute one file instead of globbing everything in the current directory and subdirectories. That's my last serious concern right now, so I'm happy signing off preemptively 🙂 Thanks for all your work on this! I think it's a fantastic improvement in the new user experience
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:50:09Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T11:09:05Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T14:09:16Z
user: login: DamianEdwards
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T18:05:32Z
user: login: RikkiGibson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T21:58:53Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T22:19:00Z
user: login: RikkiGibson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T22:24:20Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T22:39:06Z
user: login: RikkiGibson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T23:16:15Z
user: login: Forgind
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T23:24:04Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T10:17:59Z
user: login: chsienki
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-11T18:34:19Z
user: login: Forgind
body: No objections—thanks for your work on this!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:40:28Z
user: login: jjonescz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T10:07:08Z

Review comments:
diff_hunk: @@ -417,6 +449,35 @@ static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
             }
         }
 
+        /// <param name="globalProperties">
+        /// Should have <see cref="StringComparer.OrdinalIgnoreCase"/>.
+        /// </param>
+        private static void AddUserPassedProperties(Dictionary<string, string> globalProperties, string[] args)
+        {
+            var fakeCommand = new System.CommandLine.CliCommand("dotnet") { CommonOptions.PropertiesOption };
+            var propertyParsingConfiguration = new System.CommandLine.CliConfiguration(fakeCommand);
+            var propertyParseResult = propertyParsingConfiguration.Parse(args);
+            var propertyValues = propertyParseResult.GetValue(CommonOptions.PropertiesOption);
+
+            if (propertyValues != null)
+            {
+                foreach (var property in propertyValues)
+                {
+                    foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property))
+                    {
+                        if (globalProperties.TryGetValue(key, out var existingValues))
+                        {
+                            globalProperties[key] = existingValues + ";" + value;
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: Copilot
body: [nitpick] Concatenating multiple property values with a semicolon might lead to formatting issues if the existing value already ends with one; consider aggregating values in a list and joining them to ensure proper formatting.
```suggestion
                            var valuesList = existingValues.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                            valuesList.Add(value);
                            globalProperties[key] = string.Join(";", valuesList);
```
created_at: 2025-02-19T12:16:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
+        {
+            return new()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: jaredpar
body: I know this is code that is already there but surprised this is using an ordinal comparison for environment variable names. 
created_at: 2025-02-19T22:34:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jaredpar
body: Use explicit string comparison arguments
created_at: 2025-02-19T22:35:28Z
author_association: MEMBER
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: jaredpar
body: This call is now being used in a few places, sometimes to create a new dictionary and sometimes to just iterate this data which is effectively static. Did you consider instead having a definition like this: 

```csharp
 internal static ImmutableDictionary<string, string> MSBuildRequiredEnvironmentVariables { get; } = ...
```

Then in the places where a new dictionary is needed use `new (MSBuildRequiredEnvironmentVariables)` and for iteration it can just use the property directly.
created_at: 2025-02-19T22:39:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
+                    || arg.StartsWith("--binaryLogger:") || arg.Equals("--binaryLogger")
+                    || arg.StartsWith("-bl:") || arg.Equals("-bl"))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jaredpar
body: Is there a reason this overload is needed? The below one with the optional should work for the call sites I see. 
created_at: 2025-02-19T22:43:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jaredpar
body: Is this needed? The other files seem to implicitly have nullable analysis enabled
created_at: 2025-02-19T22:45:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Good idea. This code was copied from elsewhere, let me share it.

It looks like `dotnet build` is case insensitive (`dotnet build -BL` produces a binlog) whereas `dotnet run` is case sensitive (`-BL` is passed as an argument to the executed app), so I'm not sure if I should fix that inconsistency and use ignore-case comparison here to make `dotnet run -BL` produce binlog as well (but that's technically a breaking change)?
created_at: 2025-02-20T08:41:37Z
author_association: MEMBER
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
+        {
+            return new()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: jjonescz
body: Good catch, I guess I'm keeping this since it's a pre-existing behavior, but let me know if you think I should change this.
created_at: 2025-02-20T08:44:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: They unfortunately don't, only nullable *annotations* are enabled, but you get no nullable warnings without this.

Which made me realize I could also nullable-enable the RunCommand.cs file since I did a lot of changes in there too.
created_at: 2025-02-20T08:48:22Z
author_association: MEMBER
diff_hunk: @@ -417,6 +449,35 @@ static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
             }
         }
 
+        /// <param name="globalProperties">
+        /// Should have <see cref="StringComparer.OrdinalIgnoreCase"/>.
+        /// </param>
+        private static void AddUserPassedProperties(Dictionary<string, string> globalProperties, string[] args)
+        {
+            var fakeCommand = new System.CommandLine.CliCommand("dotnet") { CommonOptions.PropertiesOption };
+            var propertyParsingConfiguration = new System.CommandLine.CliConfiguration(fakeCommand);
+            var propertyParseResult = propertyParsingConfiguration.Parse(args);
+            var propertyValues = propertyParseResult.GetValue(CommonOptions.PropertiesOption);
+
+            if (propertyValues != null)
+            {
+                foreach (var property in propertyValues)
+                {
+                    foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property))
+                    {
+                        if (globalProperties.TryGetValue(key, out var existingValues))
+                        {
+                            globalProperties[key] = existingValues + ";" + value;
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: If user is passing empty properties somehow, I think that's on them. Also, this should be equivalent to pre-existing code, just moved.
created_at: 2025-02-20T08:55:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
+                    || arg.StartsWith("--binaryLogger:") || arg.Equals("--binaryLogger")
+                    || arg.StartsWith("-bl:") || arg.Equals("-bl"))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Yes, there is a delegate created from this one here:

https://github.com/dotnet/sdk/blob/8094d4938812d7ddf2a6b971d70b747fbeb3a1e6/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs#L262

The one with the optional argument wouldn't work (would need a lambda wrapper which seems less efficient).

Also this one is public whereas the one with the optional arg isn't exposed, it's private.
created_at: 2025-02-20T08:58:40Z
author_association: MEMBER
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: jjonescz
body: The default env vars cannot be static and initialized only once, they can change.
created_at: 2025-02-20T11:46:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (arg.StartsWith("/bl:") || arg.Equals("/bl")
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: Fixing this inconsistency is good.
created_at: 2025-02-26T16:35:39Z
author_association: MEMBER
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
+        {
+            return new()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: baronfel
body: Per POSIX spec, env vars with different casing should be treated as distinct:

> Environment variable names used by the utilities in the Shell and Utilities volume of POSIX.1-2017 consist solely of uppercase letters, digits, and the <underscore> ( '_' ) from the characters defined in [Portable Character Set](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap06.html#tagtcjh_3) and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. Uppercase and lowercase letters shall retain their unique identities and shall not be folded together.
created_at: 2025-02-26T16:38:48Z
author_association: MEMBER
diff_hunk: @@ -17,6 +17,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.Ordinal;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
path: src/Cli/dotnet/commands/dotnet-run/Program.cs
user: login: baronfel
body: We should do what we should have done in the first place and made a proper S.CL `Option<string>` for the binlog argument with the grammar managed inside it like we've done for the `/property` option. I'd love to see that done (so we could reuse it).
created_at: 2025-02-26T16:40:32Z
author_association: MEMBER
diff_hunk: @@ -417,6 +449,35 @@ static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
             }
         }
 
+        /// <param name="globalProperties">
+        /// Should have <see cref="StringComparer.OrdinalIgnoreCase"/>.
+        /// </param>
+        private static void AddUserPassedProperties(Dictionary<string, string> globalProperties, string[] args)
+        {
+            var fakeCommand = new System.CommandLine.CliCommand("dotnet") { CommonOptions.PropertiesOption };
+            var propertyParsingConfiguration = new System.CommandLine.CliConfiguration(fakeCommand);
+            var propertyParseResult = propertyParsingConfiguration.Parse(args);
+            var propertyValues = propertyParseResult.GetValue(CommonOptions.PropertiesOption);
+
+            if (propertyValues != null)
+            {
+                foreach (var property in propertyValues)
+                {
+                    foreach (var (key, value) in MSBuildPropertyParser.ParseProperties(property))
+                    {
+                        if (globalProperties.TryGetValue(key, out var existingValues))
+                        {
+                            globalProperties[key] = existingValues + ";" + value;
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: baronfel
body: Correct - this is safely ignorable.
created_at: 2025-02-26T16:43:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,193 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: We're in a weird state with nullability in this codebase - definitely do what you need to do in order to enable the checks on the files you're working on. We can figure the rest out later.
created_at: 2025-02-26T16:49:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: This feels gross to me - can we get by with just setting the env vars as Global Properties in the project collection?
created_at: 2025-02-26T16:51:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: nit: should use new MSBuild API from @MichalPavlik to auto-detect the use of the Console Logger vs the Terminal Logger.
created_at: 2025-02-26T16:52:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: cc @rainersigwald - potential use case for a public API to "do a restore"?
created_at: 2025-02-26T16:53:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: Ok now this is some deep magic. It looks like all of this is Restore-time stuff, do we need to add some kinds of flags/detections to enable in-memory use cases for real instead of requiring these workarounds?

cc @rainersigwald / @nkolev92 
created_at: 2025-02-26T16:55:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: Since this is in main this should be `net10.0` - we should have the current TFM available as a constant that you can interpolate into this structure.
created_at: 2025-02-26T16:56:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Yes, I think that will work fine, I went with env vars because that's what the equivalent code in MSBuildForwardingAppWithoutLogging is doing and I am unsure if there is a difference in some edge cases.
created_at: 2025-02-26T17:01:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: There is some potential for edge cases by not using env vars - some Task implementations may read the env vars directly instead of having Properties passed through to them. So there is _some_ risk there.
created_at: 2025-02-26T17:03:30Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: RikkiGibson
body: nit: `IsNullOrWhiteSpace` check seems redundant with `EndsWith` check. If it is whitespace, it will fail EndsWith check anyways. It seems reasonable to change above pattern to `args is not [string arg, ..]` then go straight to `!arg.EndsWith(...)`.
created_at: 2025-02-27T00:43:23Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: RikkiGibson
body: It doesn't seem like there's a need to enforce use of top level statements. Just compile the file as an executable and compiler will determine the entry point or error if it lacks one.

This seems to depend on some design decisions, though. If it turns out we do need to detect and enforce that this file has an entry point, it's ok to treat "explicit Main" as a stretch goal.
created_at: 2025-02-27T00:44:24Z
author_association: MEMBER
diff_hunk: @@ -417,6 +458,35 @@ static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
             }
         }
 
+        /// <param name="globalProperties">
+        /// Should have <see cref="StringComparer.OrdinalIgnoreCase"/>.
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: RikkiGibson
body: It would be good to use an assertion to enforce this, in addition to or instead of a comment.
created_at: 2025-02-27T00:46:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: It feels like this parameter should not have a default value, since this method will not be chosen when no argument is passed for this parameter.
created_at: 2025-02-27T01:09:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: It feels strange that an xml string is needed to make an in-memory project, when we are using the msbuild APIs directly for so many other things.
created_at: 2025-02-27T01:12:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: So this causes us to use the containing directory of `file.cs` to gather up `.cs` files to include in the project? Is that right?
created_at: 2025-02-27T01:18:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > It feels strange that an xml string is needed to make an in-memory project, when we are using the msbuild APIs directly for so many other things.

Isn't that same with strings containing C# that you need when using Roslyn APIs? You could create SyntaxTrees manually but no one does that. Similarly, I could create the MSBuild project without the string but seems that would be less readable.
created_at: 2025-02-27T08:19:50Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: Thanks, the `EndsWith` was added later and I didn't realize I could simplify the previous code.
created_at: 2025-02-27T08:21:13Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: This check makes a difference though. If you have `Util.cs` and `Program.cs` (where the latter has an entry point), and you execute `dotnet run Util.cs`, currently that fails but without this check it would succeed (compiler would still see the whole directory and find the entrypoint). But we want that to fail because it would be confusing if you could run `Util.cs` even though it doesn't have an entrypoint.

However, it makes me think - we could probably just pass down the file path to the compiler and it could decide whether that file really contains an entrypoint (top level statements or Main or whatever will be possible in the future) and fail otherwise for us.
created_at: 2025-02-27T08:25:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Yes, that's correct, the "implicit" or "virtual" project conceptually lives at the same level as the executed file.
created_at: 2025-02-27T08:29:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: I think I am relatively comfortable with it, especially if it remains as "just a string" and we don't start using a complex interpolated string with XML escaping handling, etc. to make the project.
created_at: 2025-02-27T14:33:45Z
author_association: MEMBER
diff_hunk: @@ -17,6 +17,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.Ordinal;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
path: src/Cli/dotnet/commands/dotnet-run/Program.cs
user: login: jjonescz
body: I don't know how to create `CliOption` that would parse `-bl` arguments properly. For example, `-bl:arg` should be valid whereas `-bl arg` should not (that should be parsed as two separate arguments). Similarly, it looks like `CliOption`s cannot be made case-insensitive.
created_at: 2025-02-27T16:24:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Then I would prefer keeping this consistent with what MSBuildForwardingAppWithoutLogging is doing - setting these as env vars.
created_at: 2025-02-27T16:25:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Can you elaborate? I couldn't find such API.
created_at: 2025-02-27T16:28:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > Ok now this is some deep magic. It looks like all of this is Restore-time stuff, do we need to add some kinds of flags/detections to enable in-memory use cases for real instead of requiring these workarounds?

Yes, ideally the targets would allow in-memory builds. Should I file an issue for it at https://github.com/NuGet/Home since the targets live there I think?
created_at: 2025-02-27T16:48:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: That would be nice.
created_at: 2025-02-27T16:49:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: Yes please - the NuGet team is currently soliciting cross-team requests for the next semester, so this would be a good candidate to ask for. 
created_at: 2025-02-27T17:34:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: Yes please - the NuGet team is currently soliciting cross-team requests for the next semester, so this would be a good candidate to ask for. 
created_at: 2025-02-27T17:34:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Filed https://github.com/NuGet/Home/issues/14148.
created_at: 2025-02-27T18:02:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Filed an issue for this one as well: https://github.com/dotnet/msbuild/issues/11519
created_at: 2025-02-27T18:12:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: What if we call static graph restore?
Is this still a problem?
https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#restoring-with-msbuild-static-graph-evaluation

cc @jeffkl 
created_at: 2025-02-27T18:22:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: static graph is not ever going to be a thing with this loose-file usage mode.
created_at: 2025-02-27T18:26:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jeffkl
body: We'll probably need to have a meeting to discuss this.  Restore itself is one hurdle but also NuGet writes out files `project.assets.json`, `project.nuget.g.props`, and `project.nuget.g.targets`.  The `.props` and `.targets` are imported by the SDK using a file pattern:

https://github.com/dotnet/msbuild/blob/41ecead49820b70e3ae6526a2ba0bec6cdcbb934/src/Tasks/Microsoft.Common.props#L74

And the `project.assets.json` is loaded by the SDK during the build to add MSBuild items on the fly that represent things to pass around:

https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.PackageDependencyResolution.targets#L266-L313

Both of these are very dependent on files on disk.  I have potential workarounds but we should chat.
created_at: 2025-02-27T18:48:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: These are in `obj` folder right? That still exists (I imagine it would be much more difficult to get rid of that), so I don't think this should be a problem.
created_at: 2025-02-28T15:09:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: not quite - the big problem is that a key file in the `obj` folder is not 'scoped' to a single project - it's called `project.assets.json`. This name is customizable though, so your in-memory project here should set that property so that we're future-proof for the "multiple apps in one directory" scenario. That property name is `ProjectAssetsFile`.

You probably want to make sure that they synthetic project name is unique-per-app too to account for MSBuild logic that does things like `$(MSBuildProjectName).foo.bar`.
created_at: 2025-02-28T15:15:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: Can you help me understand why is the scoping of the file name a problem?

@jjonescz I think it's a good to consider some sort of a design review for https://github.com/dotnet/sdk/pull/47020. 

It involves NuGet gestures, but the NuGet team hasn't really had a chance to look into that yet. 
I've tagged the appropriate people, but it's a bit of short notice for everyone to fully wrap their heads around what's going on. 
created_at: 2025-02-28T17:06:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: baronfel
body: @nkolev92 when I was looking at the `ProjectAssetsFile` logic in the targets, right next to it I saw other logic that was using the `MSBuildProjectName` to compute an intermediate file path - to me that implies that we need to be careful to ensure each 'app' has a distinct project name so that intermediates could reasonably be created in the same intermediate directory without clobbering
created_at: 2025-02-28T17:10:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!-- Override targets which don't work with project files that are not present on disk. -->
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Scoping could be a problem if we allow multiple virtual projects in one folder, but the proposal currently says they would get separate artifacts folders, so that should avoid any problems.

> short notice

I think it is expected that the implementation details of this feature might change over time, so there is no rush.
created_at: 2025-02-28T17:14:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: Static graph restore never triggers any of the target being reported as a problem, so I thought it'd just skip the problem.
Why do we have a concern with static graph?
created_at: 2025-02-28T17:26:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: hez2010
body: Should we enable `AllowUnsafeBlocks` by default?
created_at: 2025-03-04T01:36:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jaredpar
body: Think we should keep the current state where unsafe requires a specific build opt in. Overall we want to have less unsafe code in the ecosystem, not more. 
created_at: 2025-03-04T07:13:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: In general, the `dotnet run file.cs` feature aims to use the *default* project template, whatever that is. That makes it predictable.
created_at: 2025-03-04T09:17:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: https://github.com/dotnet/msbuild/pull/11318
created_at: 2025-03-04T22:01:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: Forgind
body: You can still make it static and mutable so that you only have to reinitialize it when it does change, right?
created_at: 2025-03-04T22:47:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: tiny nit:
```suggestion
            // Set up MSBuild.
```
created_at: 2025-03-04T22:48:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.OrdinalIgnoreCase;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
path: src/Cli/dotnet/commands/dotnet-run/Program.cs
user: login: Forgind
body: || arg.StartsWith("/binaryLogger:") || arg.Equals("/binaryLogger")

Casing?
created_at: 2025-03-04T22:52:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: There isn't an error for having multiple binlog arguments, but it doesn't actually make a bunch of different binlogs.

![image](https://github.com/user-attachments/assets/4c8a211e-6dc0-4edf-8252-850a051dfb59)

This should reflect that.
created_at: 2025-03-04T22:56:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: Why are we creating two separate project instances?
created_at: 2025-03-04T23:22:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: Instead of doing any of this, why can't we make a fake project then "call" RunCommand.Execute (with modified parameters) on it? That would make me feel much more comfortable.
created_at: 2025-03-04T23:25:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: This doesn't seem desirable. If you're specifically trying to build one file, there could easily be bugs (including compile-time bugs) that would prevent you from dotnet run'ning this file.
created_at: 2025-03-04T23:27:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: Why aren't you just specifying Sdk="Microsoft.NET.Sdk" in the Project element?
created_at: 2025-03-04T23:28:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: +1 to making this take into account current TF. Please don't resolve things you haven't fixed. I got here and planned to make pretty much exactly this comment.
created_at: 2025-03-04T23:28:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: I'm not convinced we should force things like nullable. You can consider making it an option, but this is doing something simple, and forcing people to be perfect on this sort of thing will just be annoying.
created_at: 2025-03-04T23:30:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: If that's true, then why did you change the default template?
created_at: 2025-03-04T23:30:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: DamianEdwards
body: We intend to match the default experience for console projects and present what we consider idiomatic defaults for modern C#, which means nullable is enabled. If people want to disable it, they can use the directive.
created_at: 2025-03-05T00:15:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: Just wanted to make sure you saw, why is static graph restore a concern? 
created_at: 2025-03-05T00:57:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.OrdinalIgnoreCase;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
path: src/Cli/dotnet/commands/dotnet-run/Program.cs
user: login: MiYanni
body: For casing, it uses `StringComparison.OrdinalIgnoreCase`.
created_at: 2025-03-05T01:01:10Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: MiYanni
body: Just for my clarification, `args is not [{ } arg, ..]` means:
- For this predicate to return *true*, `args` contains zero values (has no first value) **or** the first value is `null`.
- Therefore, when this predicate returns *false*, `args` contained at least 1 value **and** the first value is non-`null`.
- Additionally, since this is used in an `||` context, `arg` will exist in subsequent predicates when this predicate is *false*.
created_at: 2025-03-05T01:30:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Sorry, I addressed this in a similar place in tests, didn't notice this comment was on a different place.

> we should have the current TFM available as a constant that you can interpolate into this structure.

I found such variable in tests, but not in product code. However, in a follow up PR that adds "grow up" support, I'm adding a test that will verify this code matches the `dotnet new` template, so that should ensure we stay consistent.

> I got here and planned to make pretty much exactly this comment.

I would actually prefer that, reusing old threads is hard to follow.
created_at: 2025-03-05T09:10:04Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: I made minimal changes to workaround bugs that prevent it building without a physical file on disk. There is a tracking issue linked and ideally that will be fixed and the workaround can be removed and the template will exactly match the default one.
created_at: 2025-03-05T09:12:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Oh sorry, I thought you were asking what if user enabled static graph restore, whether this will work. But you are saying we should enable static graph restore and then perhaps we could avoid this workaround. Thanks, I will investigate that.
created_at: 2025-03-05T09:14:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            ReadOnlySpan<ILogger> binaryLoggers = binaryLogger is null ? [] : [binaryLogger];
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLogger is not null,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            binaryLogger?.Shutdown();
+            consoleLogger.Shutdown();
+        }
+
+        static ILogger? GetBinaryLogger(string[] args)
+        {
+            for (int i = args.Length - 1; i >= 0; i--)
+            {
+                var arg = args[i];
+                if (RunCommand.IsBinLogArgument(arg))
+                {
+                    return new BinaryLogger
+                    {
+                        Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                    };
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties = null)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: I'm not sure I follow. Are you suggesting `dotnet run file.cs` should only consider `file.cs` and not the other files in the directory? That's surely a possibility but more of a design question (see also the design doc at https://github.com/dotnet/sdk/pull/47020). In short, the current design envisions users having multiple "scripts" in one directory like `scripts/restore.cs`, `scripts/build.cs` and some shared utilities like `scripts/utils.cs` which should be accessible from the scripts.
created_at: 2025-03-05T09:19:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: 1. Performance is presumably better by invoking MSBuild in memory. We are planning to do more optimizations as well (see the design doc), e.g., skipping MSBuild invocation and invoking just csc.exe or even just the target dll. For that, we will need to communicate with MSBuild to determine the csc arguments.
2. If we create a fake project, it will show up on disk during the build time, which is not a great user experience. We could create it inside `obj` folder but that's a problem because then it has a different path which is an observable difference.
3. Hopefully, MSBuild will introduce a public API for implicit restore (see the linked tracking issue) and we can drastically simplify this.
created_at: 2025-03-05T09:28:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Because the target overrides wouldn't have an effect - the Sdk targets would be implicitly imported after the custom targets, i.e., the Sdk targets would overwrite the custom ones, but we want that the other way around.
created_at: 2025-03-05T09:30:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Yes, `dotnet run file.cs` is intended be the "default" experience. For anything else, it is recommended to migrate to project-based programs or use directives if they are available.
created_at: 2025-03-05T09:31:35Z
author_association: MEMBER
diff_hunk: @@ -545,34 +615,79 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
+
+            static string? TryFindSingleProjectInDirectory(string directory)
+            {
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
+            }
 
-            if (projectFiles.Length == 0)
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                if (args is not [var arg, ..] ||
+                    string.IsNullOrWhiteSpace(arg) ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: Yes, let me know if I should clarify that somehow.
created_at: 2025-03-05T09:32:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Because we need to set additional global properties in the restore vs the build. As far as I understand MSBuild APIs, this is the only way to do it.
created_at: 2025-03-05T09:37:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Good catch, thanks, will fix. It looks like there are some inconsistencies though - `dotnet-run` binlogs are created for each `-bl:` argument, see this function:
 https://github.com/dotnet/sdk/blob/7b44ce4a89e829b1b7ce2d1d8309289ad977da3d/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs#L374

I will change that as well to make this all consistent.
created_at: 2025-03-05T09:46:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,192 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLogger = GetBinaryLogger(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Looks like the SDK intentionally doesn't use terminal logger now, see:

https://github.com/dotnet/sdk/blob/7b44ce4a89e829b1b7ce2d1d8309289ad977da3d/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs#L507-L508

Nevertheless, I will change this PR to call the same method so when that's fixed, the new logic will use the same logger.

Btw, I think it's because the new API didn't flow here yet. I would rather not change that in this PR as it seems orthogonal.
created_at: 2025-03-05T10:25:29Z
author_association: MEMBER
diff_hunk: @@ -205,6 +199,16 @@ private static string GetDotnetPath()
             return new Muxer().MuxerPath;
         }
 
+        internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
path: src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs
user: login: jjonescz
body: I'm not sure what you mean. Are you suggesting to create a `static` field which contains a `Dictionary<string, string>` and whenever some of the inputs to this code change

```cs
{ "MSBuildExtensionsPath", MSBuildExtensionsPathTestHook ?? AppContext.BaseDirectory },
{ "MSBuildSDKsPath", GetMSBuildSDKsPath() },
{ "DOTNET_HOST_PATH", GetDotnetPath() },
```

we would change the dictionary accordingly? That sounds like introducing some error-prone logic for what I think would be a negligible benefit.

Also a static mutable field sounds problematic in case of multi-threaded access.
created_at: 2025-03-05T10:37:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: 1. Looks like static graph restore fails similarly - cannot find the project file on disk.
2. Nevertheless, I'm not sure we would want to enable it by default, doesn't it have some differences? We would ideally want to have the "default" experience with `dotnet run file.cs`.
3. Even with the current workaround, if users explicitly enable static graph restore (e.g., using `-p:` argument or `Directory.Build.props`), the `dotnet run file.cs` will fail because some other NuGet targets cannot find the project file. I guess we could try to find workarounds for that as well, but I think we can just say that's not supported for now.
created_at: 2025-03-05T11:03:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: 1. I'd be curious to see the error that's happening and where it's getting thrown from. 
These targets are not invoked at all during static graph restore, so it's surprising if the error is the same. 
2. There's no functional differences between the 2. The biggest reason why static graph is not default is because it's slower for smaller projects because of the extra process.
3. What are we saying is not supported?
created_at: 2025-03-05T17:31:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > 1. I'd be curious to see the error that's happening and where it's getting thrown from.
>    These targets are not invoked at all during static graph restore, so it's surprising if the error is the same.

The error is not exactly the same, let me try to figure out where it's coming from.

> 3\. What are we saying is not supported?

Static graph restore in combination with `dotnet run file.cs`.
created_at: 2025-03-05T17:58:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: nkolev92
body: I might be misunderstanding this scenario, but given that customers don't see a project file, I don't think they care about static graph vs regular restore, they just care that it gets compiled and run.

So I don't think we have a support story for static graph there.
created_at: 2025-03-05T18:00:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Sure, but they could do `dotnet run file.cs -p:RestoreUseStaticGraphEvaluation=true` or more likely have `RestoreUseStaticGraphEvaluation` set in a parent `Directory.Build.props` and then if such `dotnet run file.cs` fails that seems like a bug (unless we say it's unsupported for starters).
created_at: 2025-03-05T18:04:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
+                </PropertyGroup>
+
+                <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
+
+                <!--
+                    Override targets which don't work with project files that are not present on disk.
+                    See https://github.com/NuGet/Home/issues/14148.
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: So the error comes from RestoreTaskEx, not sure where exactly but found this test that shows the exact error:

https://github.com/NuGet/NuGet.Client/blob/54f4fd0b4b418666683ab717d075fe0a239eb484/test/NuGet.Core.FuncTests/Msbuild.Integration.Test/MsbuildRestoreTaskTests.cs#L821

The error is `error MSB4025: The project file could not be loaded. Could not find file '...'`.

For repro (which produces a binlog for example), you can use this branch: https://github.com/jjonescz/MSBuildInMemory/tree/static-graph
created_at: 2025-03-05T18:09:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: DamianEdwards
body: add a comment so that's clear?
created_at: 2025-03-05T20:43:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: I don't find this argument convincing.

Who wants nullability? People who work on large, complex C# projects with numerous developers.

Who doesn't want nullability? People trying to do something quickly where they don't care about the general case, only their specific case, and people who are trying to learn.

In particular, I still remember that when I was learning C#, if I saw an error message, I didn't really look at it; I just looked at my code to try to see what was wrong and added print statements if necessary. That's ok in some languages (albeit very suboptimal for C#), and it doesn't work at all for nullable.

This isn't a hill I'm willing to die on, but I do think we should be very conscious of the effect of Nullable in a context like this.
created_at: 2025-03-06T00:23:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: When I wrote that comment, I'd glanced only very briefly at RunCommand, and many of its methods operated on ProjectInstances, so my question was whether you could create a ProjectInstance and pass it to RunCommand.

I looked further just now, and though it does a lot of checks with ProjectInstances, it ultimately creates an MSBuildForwardingApp that calls MSBuild's Main method directly, and there's no overload for that that takes in a ProjectInstance.

I will caution against worrying too much about performance at this stage. RunCommand does a lot of work that you don't, and I'd rather this be correct than fast. If this is correct, why is RunCommand bulky? Could RunCommand be cut down and perhaps even made more performant if it were to follow a model similar to yours?
created_at: 2025-03-06T00:45:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: BuildRequestData has an overload with global properties
created_at: 2025-03-06T01:03:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: Sounds good. I'm not sure why someone would even want multiple binloggers attached to a single build. I can imagine wanting a separate binlog for different builds that happened as part of some script if they would overwrite each other, but making that FacadeLogger just seems wrong to me.
created_at: 2025-03-06T01:08:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,14 @@ public static RunCommand FromArgs(string[] args)
             return FromParseResult(parseResult);
         }
 
+        internal static bool IsBinLogArgument(string arg)
+        {
+            const StringComparison comp = StringComparison.OrdinalIgnoreCase;
+            return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
+                || arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
+                || arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
+        }
path: src/Cli/dotnet/commands/dotnet-run/Program.cs
user: login: Forgind
body: Completely missed that 👍
created_at: 2025-03-06T01:09:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > BuildRequestData has an overload with global properties

Those ones only take physical file paths though, right? (And they read the XML from the path passed in.) I need to use a BuildRequestData overload that takes the XML data because my project file doesn't exist on disk.
created_at: 2025-03-06T06:48:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: As far as I understand, RunCommand only uses ProjectInstances and MSBuild APIs to get the final executable path and RunArguments (users can use that to override how their project actually runs, e.g., what command line arguments are passed to it). This PR reuses all that. For the build part, RunCommand just calls MSBuild's Main method. We cannot reuse that, there is no way to pass XML project file text to MSBuild via its Main method as far as I know.
created_at: 2025-03-06T06:51:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > RunCommand does a lot of work that you don't

Can you elaborate on that? I'm not sure what work RunCommand does that this PR doesn't.

Note that this PR just hooks into the "build" stage of RunCommand. The rest (that executes the built DLL) is still the same.
created_at: 2025-03-06T06:53:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: Perhaps you are right, but why we have `<nullable>enable</nullable>` in project file templates then? Today, they are the starting point for users learning C#. Advanced users could presumably enable that on their own. That's also why `dotnet run file.cs` just reuses what `dotnet new` does - there shouldn't be much difference IMO, both are used as "the starting point".

Consider also that nullability is very difficult and error-prone to add later on, it's best to have it from the start.

Nullability is also by default just warnings, not errors, and users starting with C# could definitely ignore them.

Despite all that, I wouldn't mind not enabling nullability by default for file-based programs, that's completely valid design decision. But it seems like a big shift where we would be saying "nullability is only for advanced users" but I hear that for the first time, my feeling is that we generally recommend users to always use nullability for their new projects.
created_at: 2025-03-06T06:59:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: I think the best reason for enabling nullable in our templates is just to build awareness.

I'm mildly concerned that people who look up the nullable warnings they'd get would be told to disable it in their project files and get confused by that—but I hadn't considered that they're warnings by default. To be honest, I'm working on a Java project at the moment, and I've been consistently ignoring some of the warnings the compiler keeps telling me about—but it isn't intended to scale, so it isn't a problem.

With that in mind, I think I'd downgrade how much I care about this to only a mild preference for nullable disabled. Since it sounds like others have expressed preferences for it to stay in, I don't think this needs to change.
created_at: 2025-03-06T20:15:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: Hmm...I think you're right. You could consider adding a new API for that to MSBuild, since that seems like it would be very easy to implement, but I'd consider that a v3 change not necessary for this PR.
created_at: 2025-03-06T20:28:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: I see:
* Launch settings (which we aren't supporting here intentionally)
* Clean cancelling on ctrl-c
* Exception handling
* Restore arguments? (nologo, verbosity; I'm not sure we need this)
* User-passed properties/targets (I think there's a strong argument that we shouldn't support this)

So maybe just cleanup on ctrl-c and exceptions? For exceptions, I saw that you had the catch (Exception e) bit below; in the SDK, we normally try to get a more precise exception that includes e.Message as part of a GracefulException rather than being the whole thing printed. This isn't a huge thing in my mind, so if you want to defer it for v2, I'm fine with that.
created_at: 2025-03-06T20:45:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > would be told to disable it in their project files and get confused by that

I hope the docs mention using `#nullable disable` as an alternative.
created_at: 2025-03-10T11:09:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: jjonescz
body: > * Launch settings (which we aren't supporting here intentionally)

We support those now (although we previously didn't, you are right). The support for that is shared because it's part of the "run" phase (after the "build" phase) which is the same between both project-based and file-based projects.

> * Clean cancelling on ctrl-c

For building, I don't think that's needed for MSBuild API use - it's only useful when starting MSBuild's Main as a separate process. For running, that's again shared.

> * Exception handling

I don't think the MSBuildForwardingApp does any better exception handling:

https://github.com/dotnet/sdk/blob/9bad1ba5b9a2676f4b5b8fd7fd502f7962f6c616/src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs#L160

The reason I write the exception out is so the API can return the error code (0 or 1) and always succeed instead of throwing, ie., it cannot throw GracefulException on inner exceptions. That's so the behavior of `EnsureProjectIsBuilt` is consistent between project-based and file-based builds. I guess that's not strictly necessary, but seems nice to be consistent.

> * Restore arguments? (nologo, verbosity; I'm not sure we need this)

Verbosity logic is reused. Nologo is only needed for invocation through MSBuild's Main method, not through its APIs.

> * User-passed properties/targets

User-passed properties are supported by both project-based and file-based `dotnet run`. The logic for parsing those is shared, albeit they are of course passed differently to MSBuild Main (as command-line arguments) vs API (as dictionary).
created_at: 2025-03-10T14:09:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: DamianEdwards
body: They do, but it's fairly buried and the doc for NRT itself is pretty dense: https://learn.microsoft.com/dotnet/csharp/nullable-references#nullable-context
created_at: 2025-03-10T18:05:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: FWIW, the first web search result I got for "nullable disable" (well, the second on Google), indicates how to use the directive: https://stackoverflow.com/questions/74367986/can-you-turn-off-the-nullable-reference-type-features

Basically I am not worried that people will be confused on how to turn it off for "single file" apps.
created_at: 2025-03-10T21:58:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: > FWIW, the first web search result I got for "nullable disable" (well, the second on Google), indicates how to use the directive: https://stackoverflow.com/questions/74367986/can-you-turn-off-the-nullable-reference-type-features
> 
> Basically I am not worried that people will be confused on how to turn it off for "single file" apps.

Apologies for being direct, but this search is completely useless. You started with knowing the answer and verified that your answer was correct.

The correct search is searching for what someone who doesn't know the answer would search for. I searched for "Converting null literal or possible null value to non-nullable type." (including the quotes) on bing, and the first several things that popped up told me how to fix the problem, not how to disable it. When I finally got to something from Microsoft, it was too confusing _for me_ to figure out how to disable it.

So no; this isn't easy to find. It doesn't change that I don't think this is critical to change, but this didn't affect my feelings on that.
created_at: 2025-03-10T22:19:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: I think it's very plausible that a user who wants to disable the warning categorically will use words like "turn off', or "disable", in which case the results for the directive are quite prominent. I don't think it's a typical first resort to encountering the warning, nor should it be.

I do agree that some of Microsoft's docs are burying the top level configuration a bit here. It would be great to try and address that.
created_at: 2025-03-10T22:24:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: You're welcome to search for "Converting null literal or possible null value to non-nullable type." disable if you want. I did, and it wasn't any more useful for finding the pragma warning, though it did yield more people telling me to edit my csproj.

I think it totally implausible that a user would search for "nullable disable" when they don't know what nullable is.
created_at: 2025-03-10T22:39:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: RikkiGibson
body: I opened dotnet/docs#45188 to try and improve the situation with discoverability. Thanks!
created_at: 2025-03-10T23:16:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,194 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Collections.Immutable;
+using System.Xml;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Definition;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Tools.Run;
+
+namespace Microsoft.DotNet.Tools;
+
+/// <summary>
+/// Used to build a virtual project file in memory to support <c>dotnet run file.cs</c>.
+/// </summary>
+internal sealed class VirtualProjectBuildingCommand
+{
+    public Dictionary<string, string> GlobalProperties { get; } = new(StringComparer.OrdinalIgnoreCase);
+    public required string EntryPointFileFullPath { get; init; }
+
+    public int Execute(string[] binaryLoggerArgs, LoggerVerbosity verbosity)
+    {
+        var binaryLoggers = GetBinaryLoggers(binaryLoggerArgs);
+        var consoleLogger = new ConsoleLogger(verbosity);
+        Dictionary<string, string?> savedEnvironmentVariables = new();
+        try
+        {
+            // Set environment variables.
+            foreach (var (key, value) in MSBuildForwardingAppWithoutLogging.GetMSBuildRequiredEnvironmentVariables())
+            {
+                savedEnvironmentVariables[key] = Environment.GetEnvironmentVariable(key);
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            // Setup MSBuild.
+            var projectCollection = new ProjectCollection(
+                GlobalProperties,
+                [.. binaryLoggers, consoleLogger],
+                ToolsetDefinitionLocations.Default);
+            var parameters = new BuildParameters(projectCollection)
+            {
+                Loggers = projectCollection.Loggers,
+                LogTaskInputs = binaryLoggers.Length != 0,
+            };
+            BuildManager.DefaultBuildManager.BeginBuild(parameters);
+
+            // Do a restore first (equivalent to MSBuild's "implicit restore", i.e., `/restore`).
+            // See https://github.com/dotnet/msbuild/blob/a1c2e7402ef0abe36bf493e395b04dd2cb1b3540/src/MSBuild/XMake.cs#L1838
+            // and https://github.com/dotnet/msbuild/issues/11519.
+            var restoreRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection, addGlobalProperties: static (globalProperties) =>
+                {
+                    globalProperties["MSBuildRestoreSessionId"] = Guid.NewGuid().ToString("D");
+                    globalProperties["MSBuildIsRestoring"] = bool.TrueString;
+                }),
+                targetsToBuild: ["Restore"],
+                hostServices: null,
+                BuildRequestDataFlags.ClearCachesAfterBuild | BuildRequestDataFlags.SkipNonexistentTargets | BuildRequestDataFlags.IgnoreMissingEmptyAndInvalidImports | BuildRequestDataFlags.FailOnUnresolvedSdk);
+            var restoreResult = BuildManager.DefaultBuildManager.BuildRequest(restoreRequest);
+            if (restoreResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            // Then do a build.
+            var buildRequest = new BuildRequestData(
+                CreateProjectInstance(projectCollection),
+                targetsToBuild: ["Build"]);
+            var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
+            if (buildResult.OverallResult != BuildResultCode.Success)
+            {
+                return 1;
+            }
+
+            BuildManager.DefaultBuildManager.EndBuild();
+            return 0;
+        }
+        catch (Exception e)
+        {
+            Console.Error.WriteLine(e.Message);
+            return 1;
+        }
+        finally
+        {
+            foreach (var (key, value) in savedEnvironmentVariables)
+            {
+                Environment.SetEnvironmentVariable(key, value);
+            }
+
+            foreach (var binaryLogger in binaryLoggers)
+            {
+                binaryLogger.Shutdown();
+            }
+
+            consoleLogger.Shutdown();
+        }
+
+        static ImmutableArray<ILogger> GetBinaryLoggers(string[] args)
+        {
+            return args
+                .Where(RunCommand.IsBinLogArgument)
+                .Select(static ILogger (arg) => new BinaryLogger
+                {
+                    Parameters = arg.IndexOf(':') is >= 0 and var index
+                            ? arg[(index + 1)..]
+                            : "msbuild.binlog",
+                })
+                .ToImmutableArray();
+        }
+    }
+
+    public ProjectInstance CreateProjectInstance(ProjectCollection projectCollection)
+    {
+        return CreateProjectInstance(projectCollection, addGlobalProperties: null);
+    }
+
+    private ProjectInstance CreateProjectInstance(
+        ProjectCollection projectCollection,
+        Action<IDictionary<string, string>>? addGlobalProperties)
+    {
+        var projectRoot = CreateProjectRootElement(projectCollection);
+
+        var globalProperties = projectCollection.GlobalProperties;
+        if (addGlobalProperties is not null)
+        {
+            globalProperties = new Dictionary<string, string>(projectCollection.GlobalProperties, StringComparer.OrdinalIgnoreCase);
+            addGlobalProperties(globalProperties);
+        }
+
+        return ProjectInstance.FromProjectRootElement(projectRoot, new ProjectOptions
+        {
+            GlobalProperties = globalProperties,
+        });
+    }
+
+    private ProjectRootElement CreateProjectRootElement(ProjectCollection projectCollection)
+    {
+        var projectFileFullPath = Path.ChangeExtension(EntryPointFileFullPath, ".csproj");
+        var projectFileText = """
+            <Project>
+                <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
+
+                <PropertyGroup>
+                    <OutputType>Exe</OutputType>
+                    <TargetFramework>net9.0</TargetFramework>
+                    <ImplicitUsings>enable</ImplicitUsings>
+                    <Nullable>enable</Nullable>
path: src/Cli/dotnet/commands/VirtualProjectBuildingCommand.cs
user: login: Forgind
body: That sounds appropriate; thanks for making that. I added a comment on your issue to detail my experience as well 🙂
created_at: 2025-03-10T23:24:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -547,34 +614,78 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
 
-            if (projectFiles.Length == 0)
+            static string? TryFindSingleProjectInDirectory(string directory)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
             }
-            else if (projectFiles.Length > 1)
+
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                if (args is not [{ } arg, ..] ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
+                {
+                    throw new GracefulException(LocalizableStrings.NoTopLevelStatements, arg);
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: In theory, we could remove this restriction now and let the compiler report errors if an entrypoint is missing in the compilation (consisting of the sole input file). However, if we decide to add the support for multiple files back, we will likely want this restriction again. So, I think it's better to keep the restriction (to avoid a breaking change later) and relax it in the future if necessary.
created_at: 2025-03-11T10:17:59Z
author_association: MEMBER
diff_hunk: @@ -547,34 +614,78 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
 
-            if (projectFiles.Length == 0)
+            static string? TryFindSingleProjectInDirectory(string directory)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
             }
-            else if (projectFiles.Length > 1)
+
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                if (args is not [{ } arg, ..] ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
+                {
+                    throw new GracefulException(LocalizableStrings.NoTopLevelStatements, arg);
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: Forgind
body: I'd personally be mildly in favor of removing it mostly because I think top level statements were a bad idea in the first place. (I'm not sure how to do this, but a different check that verifies that there is an entrypoint in this file would be perfect in my view.) On the other hand, I do think your logic is sound, so I think it's ok to keep this.
created_at: 2025-03-11T19:39:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -547,34 +614,78 @@ private static void ThrowUnableToRunError(ProjectInstance project)
                         project.GetPropertyValue("OutputType")));
         }
 
-        private string DiscoverProjectFilePath(string? projectFileOrDirectoryPath)
+        private static string? DiscoverProjectFilePath(string? projectFileOrDirectoryPath, ref string[] args, out string? entryPointFilePath)
         {
+            bool emptyProjectOption = false;
             if (string.IsNullOrWhiteSpace(projectFileOrDirectoryPath))
             {
+                emptyProjectOption = true;
                 projectFileOrDirectoryPath = Directory.GetCurrentDirectory();
             }
 
-            if (Directory.Exists(projectFileOrDirectoryPath))
+            string? projectFilePath = Directory.Exists(projectFileOrDirectoryPath)
+                ? TryFindSingleProjectInDirectory(projectFileOrDirectoryPath)
+                : projectFileOrDirectoryPath;
+
+            // If no project exists in the directory and no --project was given,
+            // try to resolve an entry-point file instead.
+            entryPointFilePath = projectFilePath is null && emptyProjectOption
+                ? TryFindEntryPointFilePath(ref args)
+                : null;
+
+            if (entryPointFilePath is null && projectFilePath is null)
             {
-                projectFileOrDirectoryPath = FindSingleProjectInDirectory(projectFileOrDirectoryPath);
+                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, projectFileOrDirectoryPath, "--project");
             }
-            return projectFileOrDirectoryPath;
-        }
 
-        public static string FindSingleProjectInDirectory(string directory)
-        {
-            string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+            return projectFilePath;
 
-            if (projectFiles.Length == 0)
+            static string? TryFindSingleProjectInDirectory(string directory)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionNoProjects, directory, "--project");
+                string[] projectFiles = Directory.GetFiles(directory, "*.*proj");
+
+                if (projectFiles.Length == 0)
+                {
+                    return null;
+                }
+
+                if (projectFiles.Length > 1)
+                {
+                    throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                }
+
+                return projectFiles[0];
             }
-            else if (projectFiles.Length > 1)
+
+            static string? TryFindEntryPointFilePath(ref string[] args)
             {
-                throw new GracefulException(LocalizableStrings.RunCommandExceptionMultipleProjects, directory);
+                if (args is not [{ } arg, ..] ||
+                    !arg.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
+                    !File.Exists(arg))
+                {
+                    return null;
+                }
+
+                if (!HasTopLevelStatements(arg))
+                {
+                    throw new GracefulException(LocalizableStrings.NoTopLevelStatements, arg);
path: src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
user: login: jjonescz
body: > I'm not sure how to do this, but a different check that verifies that there is an entrypoint in this file would be perfect in my view.

Compiler would do this check automatically for us because we set OutputType=Exe.
created_at: 2025-03-12T10:07:08Z
author_association: MEMBER

PR comments:
user: login: jjonescz
created_at: 2025-02-25T16:19:47Z
body: @MiYanni @RikkiGibson @chsienki for reviews, thanks
author_association: MEMBER
user: login: hez2010
created_at: 2025-03-04T01:38:13Z
body: I believe the most usage would be scripting with shebang syntax. Should we run it with `Release` config by default to avoid unnecessary by-default slow down?
author_association: NONE
user: login: jjonescz
created_at: 2025-03-04T09:19:04Z
body: > I believe the most usage would be scripting with shebang syntax. Should we run it with `Release` config by default to avoid unnecessary by-default slow down?

I would rather not change any defaults to avoid confusion. However, if you are using shebang, presumably you will specify the Release config there:

```cs
#!/usr/bin/dotnet run -c Release
```
author_association: MEMBER
user: login: jjonescz
created_at: 2025-03-11T18:31:34Z
body: @MiYanni @Forgind @chsienki @RikkiGibson. I've updated this PR to limit to single file instead of globbing. Please take a look if you want, I'd like to merge if there are no objections and follow up with other PRs (like grow up command and `#package` directive). Thanks.
author_association: MEMBER