如何以非提升的方式启动一个进程

9

我的应用程序以highestAvailable的权限级别运行。

如何以非管理员权限运行一个进程?

我尝试了下面的方法,但是并没有起作用:

Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})

我尝试了以下信任级别来使用Win32 API启动我的进程,但是它们都没有正确工作:
0
1260: This program is blocked by group policy. For more information, contact your system administrator.

0x1000
The application was unable to start correctly (0xc0000142). Click OK to close the application. 

0x10000
Process starts then hangs

0x20000
All options are not available

0x40000
Runs as admin

如果我从我的提升的应用程序中运行 tskill foo,它将使用正确的权限重新启动 foo。
我需要的是一种解决方案,在其中我不必指定信任级别。该进程应自动以正确的信任级别启动,就像 tskill 工具在正确的信任级别下重新启动 foo.exe 一样。用户选择并运行 foo.exe,因此它可以是任何东西。
如果我能以某种方式获取进程的信任级别,我就可以轻松地做到这一点,因为当我的应用程序能够捕获其信任级别时,foo.exe 就会运行。

可能是这个问题的重复:https://dev59.com/tXM_5IYBdhLWcg3w6HvT - Rob
1
你的做法是错误的。让主进程能够以非管理员身份运行,然后提升一个或多个子进程来执行特权任务。这样可以解决这个问题和其他一些问题。 - Cody Gray
另外,我看到你开始了一个悬赏。为什么?你不同意这个问题是一年前Rob建议的那个问题的重复吗?你还在遇到什么问题? - Cody Gray
未来的读者应该注意,Cody的建议仍然是最佳方案。如果您正确地设计了应用程序,就不需要多次获得UAC批准。 - Harry Johnston
这个回答解决了你的问题吗?如何为子进程取消提升特权 - Paul
显示剩余2条评论
4个回答

12

以下是我通过克隆资源管理器的令牌来获得最佳结果的方法:

var shellWnd = WinAPI.GetShellWindow();
if (shellWnd == IntPtr.Zero)
    throw new Exception("Could not find shell window");

uint shellProcessId;
WinAPI.GetWindowThreadProcessId(shellWnd, out shellProcessId);

var hShellProcess = WinAPI.OpenProcess(0x00000400 /* QueryInformation */, false, shellProcessId);

var hShellToken = IntPtr.Zero;
if (!WinAPI.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
    throw new Win32Exception();

uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var hToken = IntPtr.Zero;
WinAPI.DuplicateTokenEx(hShellToken, tokenAccess, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken);

var pi = new WinAPI.PROCESS_INFORMATION();
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WinAPI.CreateProcessWithTokenW(hToken, 0, null, cmdArgs, 0, IntPtr.Zero, null, ref si, out pi))
    throw new Win32Exception();

替代方案

起初我选择了drf的出色答案,但进行了一些扩展。如果上面(克隆Explorer的令牌)不符合您的喜好,请继续阅读但是注意到最后有一个陷阱

按照drf描述的方法使用时,该过程是在没有管理员访问权限的情况下启动的,但它仍具有很高的完整性级别。典型的未提升的进程具有中等完整性级别。

请尝试这样做:使用Process Hacker查看以这种方式启动的进程的属性;您将看到PH认为该进程已被提升,即使它没有管理员访问权限。添加完整性列,您将看到其为“高”。

修复方法相当简单:在使用SaferComputeTokenFromLevel后,我们需要将令牌完整性级别更改为“中”。执行此操作的代码可能如下所示(从MSDN示例转换而来):

// Get the Medium Integrity SID
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
    throw new Win32Exception();

// Construct a structure describing the token integrity level
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);

// Modify the token
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL,
                                (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>()
                                + WinAPI.GetLengthSid(pMediumIntegritySid)))
    throw new Win32Exception();

哎呀,这仍然不能完全解决问题。实际上,该进程将不具有管理员访问权限;它将没有高完整性,但是它仍将拥有一个标记为“提升”的令牌。

无论这对您是否构成问题我不知道,但这可能是我最终克隆资源管理器令牌的原因,就像在此答案开头所描述的那样。


以下是我完整的源代码(改自 drf 的答案),展示了所有P/Invoke的辉煌:

var hSaferLevel = IntPtr.Zero;
var hToken = IntPtr.Zero;
var pMediumIntegritySid = IntPtr.Zero;
var pTIL = IntPtr.Zero;
var pi = new WinAPI.PROCESS_INFORMATION();
try
{
    var si = new WinAPI.STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    var processAttributes = new WinAPI.SECURITY_ATTRIBUTES();
    var threadAttributes = new WinAPI.SECURITY_ATTRIBUTES();
    var args = CommandRunner.ArgsToCommandLine(Args);

    if (!WinAPI.SaferCreateLevel(WinAPI.SaferScopes.User, WinAPI.SaferLevels.NormalUser, 1, out hSaferLevel, IntPtr.Zero))
        throw new Win32Exception();

    if (!WinAPI.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, WinAPI.SaferComputeTokenFlags.None, IntPtr.Zero))
        throw new Win32Exception();

    if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
        throw new Win32Exception();
    var TIL = new TOKEN_MANDATORY_LABEL();
    TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
    TIL.Label.Sid = pMediumIntegritySid;
    pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
    Marshal.StructureToPtr(TIL, pTIL, false);
    if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL, (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + WinAPI.GetLengthSid(pMediumIntegritySid)))
        throw new Win32Exception();

    if (!WinAPI.CreateProcessAsUser(hToken, null, commandLine, ref processAttributes, ref threadAttributes, true, 0, IntPtr.Zero, null, ref si, out pi))
        throw new Win32Exception();
}
finally
{
    if (hToken != IntPtr.Zero && !WinAPI.CloseHandle(hToken))
        throw new Win32Exception();
    if (pMediumIntegritySid != IntPtr.Zero && WinAPI.LocalFree(pMediumIntegritySid) != IntPtr.Zero)
        throw new Win32Exception();
    if (pTIL != IntPtr.Zero)
        Marshal.FreeHGlobal(pTIL);
    if (pi.hProcess != IntPtr.Zero && !WinAPI.CloseHandle(pi.hProcess))
        throw new Win32Exception();
    if (pi.hThread != IntPtr.Zero && !WinAPI.CloseHandle(pi.hThread))
        throw new Win32Exception();
}

这里是你需要额外添加的P/Invoke定义,除了drf的答案中列出的之外:

And here are the P/Invoke definitions you'll need in addition to those listed in drf's answer:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass,
    IntPtr TokenInformation, UInt32 TokenInformationLength);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);

[DllImport("advapi32.dll")]
public static extern uint GetLengthSid(IntPtr pSid);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
    string StringSid,
    out IntPtr ptrSid);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);

2
TOKEN_MANDATORY_LABEL 的定义在哪里? var args = CommandRunner.ArgsToCommandLine(Args); 这一行的目的是什么?如果您的“完整源代码”实际上是完整的源代码,那将非常好,这样我们就不必花费数小时从drf的答案中拼凑东西并在线查找缺少的定义。 - Joe Uhren
@JoeyJoeJoeJrShabadoo 如果人们能够认真对待免费提供的答案也会非常好。我花了几个小时来解决这个问题。之后我抽出一些空余时间写下了我找到的内容。但你仍然不满意。请友善地指出缺陷,并将其纠正。甚至可以帮助一下,提出修改建议,而不是抱怨。 - Roman Starkov
抱歉听起来有些不感激。我也花了很多时间回答SO的问题,我知道像这样精心制作答案需要多少时间。但是昨晚我花了一个多小时来组合你的代码,结果发现它并不能解决我的问题。即使你的答案比drf的“更正确”,它仍然是不完整和无用的。你可以怎么理解我的批评,但我仍然认为你在这里的回答没有帮助,只是浪费了我的时间。我的目标是通过这些评论要么促使你改正,要么警告其他人远离。 - Joe Uhren
1
@JoeyJoeJoeJrShabadoo 好的。就我个人而言,我建议克隆Explorer的令牌;另一种方法更加复杂,而且没有完全成功地生成具有正确属性的令牌。 - Roman Starkov

12

Win32安全管理函数可以创建一个具有普通用户权限的受限令牌;使用该令牌,您可以调用CreateProcessAsUser以该令牌运行进程。以下是一个概念验证,它以普通用户身份运行cmd.exe,无论该进程是否在提升的上下文中运行。

// Initialize variables.  
IntPtr hSaferLevel, hToken;
STARTUPINFO si = default(STARTUPINFO);
SECURITY_ATTRIBUTES processAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES threadAttributes = default(SECURITY_ATTRIBUTES);
PROCESS_INFORMATION pi;
si.cb = Marshal.SizeOf(si);

// The process to start (for demonstration, cmd.exe)
string ProcessName = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.System),
    "cmd.exe");

// Create the restricted token info
if (!SaferCreateLevel(
     SaferScopes.User,
     SaferLevels.NormalUser, // Program will execute as a normal user
     1, // required
     out hSaferLevel,
     IntPtr.Zero))
         throw new Win32Exception(Marshal.GetLastWin32Error());

// From the level create a token
if (!SaferComputeTokenFromLevel(
     hSaferLevel,
     IntPtr.Zero,
     out hToken,
     SaferComputeTokenFlags.None,
     IntPtr.Zero))
         throw new Win32Exception(Marshal.GetLastWin32Error());

// Run the process with the restricted token
if (!CreateProcessAsUser(
     hToken,
     ProcessName,
     null, ref processAttributes, ref threadAttributes,
     true, 0, IntPtr.Zero, null,
     ref si, out pi))
         throw new Win32Exception(Marshal.GetLastWin32Error());

 // Cleanup
 if (!CloseHandle(pi.hProcess))
     throw new Win32Exception(Marshal.GetLastWin32Error());
 if (!CloseHandle(pi.hThread))
     throw new Win32Exception(Marshal.GetLastWin32Error());
 if (!SaferCloseLevel(hSaferLevel))
     throw new Win32Exception(Marshal.GetLastWin32Error());

这种方法使用以下Win32函数:

  • SaferIdentifyLevel用于指示标识级别(受限、普通或提升)。将levelId设置为SAFER_LEVELID_NORMALUSER(0x20000)提供了普通用户级别。
  • SaferComputeTokenFromLevel为提供的级别创建一个令牌。将InAccessToken参数传递为NULL将使用当前线程的身份。
  • CreateProcessAsUser使用提供的令牌创建进程。由于会话已经是交互式的,因此大多数参数可以保持默认值。(第三个参数lpCommandLine可以提供字符串来指定命令行。)
  • CloseHandle(Kernel32)SaferCloseLevel用于释放已分配的内存。

最后,下面是P / Invoke代码(大部分来自pinvoke.net):

[Flags]
public enum SaferLevels : uint
{
    Disallowed = 0,
    Untrusted = 0x1000,
    Constrained = 0x10000,
    NormalUser = 0x20000,
    FullyTrusted = 0x40000
}

[Flags]
public enum SaferComputeTokenFlags : uint
{
    None = 0x0,
    NullIfEqual = 0x1,
    CompareOnly = 0x2,
    MakeIntert = 0x4,
    WantFlags = 0x8
}

[Flags]
public enum SaferScopes : uint
{
    Machine = 1,
    User = 2
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwYSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}


[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
    IntPtr hToken,
    string lpApplicationName,
    string lpCommandLine,
    ref SECURITY_ATTRIBUTES lpProcessAttributes,
    ref SECURITY_ATTRIBUTES lpThreadAttributes,
    bool bInheritHandles,
    uint dwCreationFlags,
    IntPtr lpEnvironment,
    string lpCurrentDirectory,
    ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation); 

[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
    SaferScopes dwScopeId,
    SaferLevels dwLevelId,
    int OpenFlags,
    out IntPtr pLevelHandle,
    IntPtr lpReserved);

[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
    IntPtr pLevelHandle);

[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
  IntPtr levelHandle,
  IntPtr inAccessToken,
  out IntPtr outAccessToken,
  SaferComputeTokenFlags dwFlags,
  IntPtr lpReserved
);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

这是一个重复的问题: https://dev59.com/XXVC5IYBdhLWcg3wvT7g,但如果您不需要模拟上下文,只需要取消提升权限,那么我想这会起作用。此外,应该在某个地方链接到此处: http://msdn.microsoft.com/en-us/library/windows/desktop/ms721849%28v=vs.85%29.aspx#safer_functions - porkchop
将MSDN VB.net示例项目链接在此处:http://code.msdn.microsoft.com/windowsdesktop/VBUACSelfElevation-4c014507 <- Windows UAC自我提升。 - porkchop
1
这个可以工作,但是产生的进程将从其父进程继承高完整性。请参见我的答案,以获取启动中间完整性的子进程的附加代码,这是一个未提升的进程通常具有的完整性等级。 - Roman Starkov
不确定为什么使用SaferComputeTokenFromLevel计算的新令牌仍被标记为“提升”。任何尝试从生成的进程再次提升都将失败。 - Gerardo Grignoli
如果父进程已经提升,那么此方法将无效,正如在此处所评论的:https://dev59.com/XXVC5IYBdhLWcg3wvT7g#287072。Process Hacker 仍然显示结果进程为 Elevation=Full。这种方法按预期工作:https://dev59.com/5KTja4cB1Zd3GeqPF7Ea#45921237。 - Andry

5

Raymond Chen在他的博客中解释了这个问题:

如何从我的提权进程启动非提权进程,反之亦然?

在GitHub上搜索C#版本的该代码时,我在微软的Visual Studio的Node.js工具库中找到了以下实现:SystemUtilities.cs(请参见ExecuteProcessUnElevated函数)。

以防文件消失,请在此处查看文件内容:

// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Runtime.InteropServices;

namespace Microsoft.NodejsTools.SharedProject
{
    /// <summary>
    /// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
    /// </summary>
    internal class SystemUtility
    {
        /// <summary>
        /// We are elevated and should launch the process unelevated. We can't create the
        /// process directly without it becoming elevated. So to workaround this, we have
        /// explorer do the process creation (explorer is typically running unelevated).
        /// </summary>
        internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
        {
            var shellWindows = (IShellWindows)new CShellWindows();

            // Get the desktop window
            object loc = CSIDL_Desktop;
            object unused = new object();
            int hwnd;
            var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);

            // Get the shell browser
            var serviceGuid = SID_STopLevelBrowser;
            var interfaceGuid = typeof(IShellBrowser).GUID;
            var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);

            // Get the shell dispatch
            var dispatch = typeof(IDispatch).GUID;
            var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
            var shellDispatch = (IShellDispatch2)folderView.Application;

            // Use the dispatch (which is unelevated) to launch the process for us
            shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
        }

        /// <summary>
        /// Interop definitions
        /// </summary>
        private const int CSIDL_Desktop = 0;
        private const int SWC_DESKTOP = 8;
        private const int SWFO_NEEDDISPATCH = 1;
        private const int SW_SHOWNORMAL = 1;
        private const int SVGIO_BACKGROUND = 0;
        private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");

        [ComImport]
        [Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
        [ClassInterfaceAttribute(ClassInterfaceType.None)]
        private class CShellWindows
        {
        }

        [ComImport]
        [Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IShellWindows
        {
            [return: MarshalAs(UnmanagedType.IDispatch)]
            object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
        }

        [ComImport]
        [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IServiceProvider
        {
            [return: MarshalAs(UnmanagedType.Interface)]
            object QueryService(ref Guid guidService, ref Guid riid);
        }

        [ComImport]
        [Guid("000214E2-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellBrowser
        {
            void VTableGap01(); // GetWindow
            void VTableGap02(); // ContextSensitiveHelp
            void VTableGap03(); // InsertMenusSB
            void VTableGap04(); // SetMenuSB
            void VTableGap05(); // RemoveMenusSB
            void VTableGap06(); // SetStatusTextSB
            void VTableGap07(); // EnableModelessSB
            void VTableGap08(); // TranslateAcceleratorSB
            void VTableGap09(); // BrowseObject
            void VTableGap10(); // GetViewStateStream
            void VTableGap11(); // GetControlWindow
            void VTableGap12(); // SendControlMsg
            IShellView QueryActiveShellView();
        }

        [ComImport]
        [Guid("000214E3-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellView
        {
            void VTableGap01(); // GetWindow
            void VTableGap02(); // ContextSensitiveHelp
            void VTableGap03(); // TranslateAcceleratorA
            void VTableGap04(); // EnableModeless
            void VTableGap05(); // UIActivate
            void VTableGap06(); // Refresh
            void VTableGap07(); // CreateViewWindow
            void VTableGap08(); // DestroyViewWindow
            void VTableGap09(); // GetCurrentInfo
            void VTableGap10(); // AddPropertySheetPages
            void VTableGap11(); // SaveViewState
            void VTableGap12(); // SelectItem

            [return: MarshalAs(UnmanagedType.Interface)]
            object GetItemObject(UInt32 aspectOfView, ref Guid riid);
        }

        [ComImport]
        [Guid("00020400-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IDispatch
        {
        }

        [ComImport]
        [Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IShellFolderViewDual
        {
            object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        }

        [ComImport]
        [Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IShellDispatch2
        {
            void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
        }
    }
}

3

最简单的解决方案是使用 explorer.exe 启动进程。这将以非管理员权限启动任何进程。您只需使用以下命令启动 explorer.exe:

    System.Diagnostics.Process.Start();

文件名将是"C:\Windows\explorer.exe",参数将是您想要非管理员身份启动的可执行文件,用引号括起来。
例子:
如果我想以非管理员身份启动 F:\folder\example.exe,则应这样做:
    using System.Diagnostics;

    namespace example
    {
        class exampleClass
        {
            ProcessStartInfo exampleStartInfo = new ProcessStartInfo();
            exampleStartInfo.FileName = "C:\\Windows\\explorer.exe";
            exampleStartInfo.Arguments = "\"F:\\folder\\example.exe\"";
            Process.Start(exampleStartInfo);
        }
    }

这可能在较旧版本的Windows上无法运行,但至少在我的笔记本电脑上可以工作,因此它在Windows 10上肯定可以工作。


1
你不能使用这种方法传递参数,对吧?而且在Windows 10中,使用“.BAT”文件也不起作用。 - Gerardo Grignoli
@GerardoGrignoli,不,你需要绕过这个问题,例如使用某种形式的IPC/tcp套接字、命名管道、编写带有参数的文件/注册表、设置剪贴板文本,甚至创建一个以所需参数(base64)命名的可执行文件并启动它(该可执行文件解析/取消base64编码并启动实际的可执行文件)。 - Caius Jard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接