diff --git a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata
index 4bbdccae19d803..5d45345491c369 100644
--- a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata
+++ b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata
@@ -1,3 +1,7 @@
 
 normaliz.dll!IsNormalizedString
 normaliz.dll!NormalizeString
+
+
+user32.dll!GetProcessWindowStation
+user32.dll!GetUserObjectInformationW
diff --git a/src/libraries/Common/src/Interop/Windows/User32/Interop.Constants.cs b/src/libraries/Common/src/Interop/Windows/User32/Interop.Constants.cs
index 45fce95f67a569..5566ba2b2802fd 100644
--- a/src/libraries/Common/src/Interop/Windows/User32/Interop.Constants.cs
+++ b/src/libraries/Common/src/Interop/Windows/User32/Interop.Constants.cs
@@ -225,5 +225,7 @@ internal partial class User32
 
         public const int UOI_FLAGS = 1;
 
+        public const int HWND_BROADCAST = 0xffff;
+
     }
 }
diff --git a/src/libraries/Common/src/Interop/Windows/User32/Interop.GetUserObjectInformation.cs b/src/libraries/Common/src/Interop/Windows/User32/Interop.GetUserObjectInformation.cs
index 85205d3570a1ee..3144de671e3042 100644
--- a/src/libraries/Common/src/Interop/Windows/User32/Interop.GetUserObjectInformation.cs
+++ b/src/libraries/Common/src/Interop/Windows/User32/Interop.GetUserObjectInformation.cs
@@ -10,6 +10,6 @@ internal partial class Interop
     internal partial class User32
     {
         [DllImport(Libraries.User32, SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
-        public static extern bool GetUserObjectInformationW(IntPtr hObj, int nIndex, ref USEROBJECTFLAGS pvBuffer, int nLength, ref int lpnLengthNeeded);
+        public static extern unsafe bool GetUserObjectInformationW(IntPtr hObj, int nIndex, void* pvBuffer, uint nLength, ref uint lpnLengthNeeded);
     }
 }
diff --git a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
index 6e24b137a806d6..ca6c3b5c38bd6e 100644
--- a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
+++ b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
@@ -77,32 +77,24 @@ private static unsafe bool UserInteractive
         {
             get
             {
-                if (Environment.OSVersion.Platform == System.PlatformID.Win32NT)
+                IntPtr hwinsta = Interop.User32.GetProcessWindowStation();
+                if (hwinsta != IntPtr.Zero && s_processWinStation != hwinsta)
                 {
-                    IntPtr hwinsta = IntPtr.Zero;
-
-                    hwinsta = Interop.User32.GetProcessWindowStation();
-                    if (hwinsta != IntPtr.Zero && s_processWinStation != hwinsta)
-                    {
-                        s_isUserInteractive = true;
+                    s_isUserInteractive = true;
 
-                        int lengthNeeded = 0;
-                        Interop.User32.USEROBJECTFLAGS flags = default;
+                    uint dummy = 0;
+                    Interop.User32.USEROBJECTFLAGS flags = default;
 
-                        if (Interop.User32.GetUserObjectInformationW(hwinsta, Interop.User32.UOI_FLAGS, ref flags, sizeof(Interop.User32.USEROBJECTFLAGS), ref lengthNeeded))
+                    if (Interop.User32.GetUserObjectInformationW(hwinsta, Interop.User32.UOI_FLAGS, &flags, (uint)sizeof(Interop.User32.USEROBJECTFLAGS), ref dummy))
+                    {
+                        if ((flags.dwFlags & Interop.User32.WSF_VISIBLE) == 0)
                         {
-                            if ((flags.dwFlags & Interop.User32.WSF_VISIBLE) == 0)
-                            {
-                                s_isUserInteractive = false;
-                            }
+                            s_isUserInteractive = false;
                         }
-                        s_processWinStation = hwinsta;
                     }
+                    s_processWinStation = hwinsta;
                 }
-                else
-                {
-                    s_isUserInteractive = true;
-                }
+
                 return s_isUserInteractive;
             }
         }
diff --git a/src/libraries/System.Private.CoreLib/src/Interop/Windows/User32/Interop.Constants.cs b/src/libraries/System.Private.CoreLib/src/Interop/Windows/User32/Interop.Constants.cs
deleted file mode 100644
index a48329fb6b191a..00000000000000
--- a/src/libraries/System.Private.CoreLib/src/Interop/Windows/User32/Interop.Constants.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-internal static partial class Interop
-{
-    internal static partial class User32
-    {
-        internal const int HWND_BROADCAST = 0xffff;
-        internal const int WM_SETTINGCHANGE = 0x001A;
-    }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 41360b2000db4f..cf8edf1de1e00a 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1325,11 +1325,22 @@
     
       Common\Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs
     
-    
+    
+      Common\Interop\Windows\User32\Interop.Constants.cs
+    
     
       Common\Interop\Windows\User32\Interop.LoadString.cs
     
     
+    
+      Common\Interop\Windows\User32\Interop.GetProcessWindowStation.cs
+    
+    
+      Common\Interop\Windows\User32\Interop.GetUserObjectInformation.cs
+    
+    
+      Common\Interop\Windows\User32\Interop.USEROBJECTFLAGS.cs
+    
     
     
     
diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs
index d733feb5316221..3a51a58fc91a3a 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs
@@ -17,6 +17,8 @@ public static partial class Environment
     {
         private static Func? s_directoryCreateDirectory;
 
+        public static bool UserInteractive => true;
+
         private static string CurrentDirectoryCore
         {
             get => Interop.Sys.GetCwd();
diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
index 17a49080d661a3..98c913a09804e1 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
@@ -122,6 +122,28 @@ public static string SystemDirectory
             }
         }
 
+        public static unsafe bool UserInteractive
+        {
+            get
+            {
+                // Per documentation of GetProcessWindowStation, this handle should not be closed
+                IntPtr handle = Interop.User32.GetProcessWindowStation();
+                if (handle != IntPtr.Zero)
+                {
+                    Interop.User32.USEROBJECTFLAGS flags = default;
+                    uint dummy = 0;
+                    if (Interop.User32.GetUserObjectInformationW(handle, Interop.User32.UOI_FLAGS, &flags, (uint)sizeof(Interop.User32.USEROBJECTFLAGS), ref dummy))
+                    {
+                        return ((flags.dwFlags & Interop.User32.WSF_VISIBLE) == 0);
+                    }
+                }
+
+                // If we can't determine, return true optimistically
+                // This will include cases like Windows Nano which do not expose WindowStations
+                return true;
+            }
+        }
+
         public static unsafe long WorkingSet
         {
             get
diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs
index 1454f1d56e4040..194ae6099bb513 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs
@@ -138,8 +138,6 @@ public static OperatingSystem OSVersion
             }
         }
 
-        public static bool UserInteractive => true;
-
         public static Version Version
         {
             get
diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs
index ea33c8e9eda47e..26571f9697a5a8 100644
--- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs
+++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs
@@ -165,8 +165,23 @@ public void SystemPageSize_Valid()
         }
 
         [Fact]
-        public void UserInteractive_True()
+        [PlatformSpecific(TestPlatforms.AnyUnix)]
+        public void UserInteractive_Unix_True()
+        {
+            Assert.True(Environment.UserInteractive);
+        }
+
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public void UserInteractive_Windows_DoesNotThrow()
+        {
+            var dummy = Environment.UserInteractive; // Does not throw
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindowsNanoServer))]
+        public void UserInteractive_WindowsNano()
         {
+            // Defaults to true on Nano, because it doesn't expose WindowStations
             Assert.True(Environment.UserInteractive);
         }
 
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
index c6c43dce26b5dd..8b62f638a9aff3 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
@@ -142,7 +142,16 @@ public void TestOnExecuteCustomCommand()
             ServiceController controller = ConnectToServer();
 
             controller.ExecuteCommand(128);
-            Assert.Equal(128, _testService.GetByte());
+            // Response from test service:
+            //  128 => Environment.UserInteractive == false
+            //  129 => Environment.UserInteractive == true
+            //
+            // On Windows Nano and other SKU that do not expose Window Stations, Environment.UserInteractive
+            // will always return true, even within a service process.
+            // Otherwise, we expect it to be false.
+            // (This is the only place we verify Environment.UserInteractive can return false)
+            byte expected = PlatformDetection.HasWindowsShell ? (byte)128 : (byte)129;
+            Assert.Equal(expected, _testService.GetByte());
 
             controller.Stop();
             Assert.Equal((int)PipeMessageByteCode.Stop, _testService.GetByte());
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
index c50f129e8ea0bf..604289322f9ce8 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
@@ -43,6 +43,10 @@ protected override void OnContinue()
         protected override void OnCustomCommand(int command)
         {
             base.OnCustomCommand(command);
+
+            if (Environment.UserInteractive) // see ServiceBaseTests.TestOnExecuteCustomCommand()
+                command++;
+
             WriteStreamAsync(PipeMessageByteCode.OnCustomCommand, command).Wait();
         }