【问题标题】:How to Start a Process Unelevated如何启动未提升的进程
【发布时间】:2013-07-19 21:20:05
【问题描述】:

我的应用以requestedExecutionLevel 运行,设置为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 在我的应用程序可以捕获其信任级别时运行。

【问题讨论】:

  • 你做错了。使主进程能够运行未提升,然后提升一个或多个子进程以执行特权任务。这解决了这个问题,以及许多其他问题。
  • 另外,我看到你已经开始赏金了。为什么?您是否不同意这个问题与 Rob 一年前提出的问题重复?你还在纠结什么问题?
  • @CodyGray 我已经更新了这个问题。反之则不适合我的情况,用户将不得不处理 UAC 数十次。

标签: c# .net vb.net elevation process-elevation


【解决方案1】:

我通过克隆 Explorer 的令牌获得了最好的结果,如下所示:

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 认为该进程已被提升,即使它没有管理访问权限。添加一个 Integrity 列,您会看到它是“高”。

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

// 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();

唉,这仍然没有真正完全解决问题。该进程将没有管理权限;它不会有很高的完整性,但它会仍然有一个标记为“提升”的令牌。

这对你来说是否是个问题我不知道,但这可能是我最终克隆 Explorer 令牌的原因,如本答案开头所述。


这是我的完整源代码(修改后的 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();
}

除了 drf 的回答中列出的那些之外,这里是您需要的 P/Invoke 定义:

[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);

【讨论】:

  • TOKEN_MANDATORY_LABEL 的定义在哪里? var args = CommandRunner.ArgsToCommandLine(Args); 行的目的是什么?如果您的“完整源代码”实际上是完整的源代码,那就太好了,这样我们就不必花费数小时从 drf 的答案中拼凑出来并在线查找缺失的定义。
  • @JoeyJoeJoeJrShabadoo 如果人们免费提供他们的答案,那也很棒。我花了几个小时试图弄清楚这一点。然后,我也找到了一些空闲时间来写下我的发现。而你还是不开心。 很好地指出缺点,它会得到解决。甚至可以提供一点帮助,提出修改建议而不是抱怨?
  • 我很抱歉听起来忘恩负义。我也花了很多时间回馈 SO,我知道必须花费多少时间才能做出这样的答案。但是我昨晚花了一个多小时把你的代码放在一起,结果发现它并没有解决我的问题。即使您的答案比 drf 的“更正确”,它也是不完整且无法使用的。随心所欲地接受我的批评,但我仍然认为您在这里的回答没有多大帮助,因为它只会浪费我的时间。我对这些 cmets 的目标是要么推动你纠正它,要么警告其他人远离
  • @JoeyJoeJoeJrShabadoo 够公平的。 FWIW,我建议克隆 Explorer 的令牌;另一种方法要复杂得多,并且相当无法生成具有正确属性的令牌。
【解决方案2】:

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 为提供的关卡创建一个令牌。将NULL 传递给 InAccessToken 参数使用当前线程的标识。
  • 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);

【讨论】:

  • 这是:stackoverflow.com/questions/196949/… 的副本,但是,如果您不需要模拟,只需不提升,那么我想这会起作用。另外,这应该链接到某个地方:msdn.microsoft.com/en-us/library/windows/desktop/…
  • @porkchop 该方法在Writing Secure Code, Second Edition,第 7 章,第 241-243 页中的 C++ 示例代码中描述(并采用)。 C#代码是原始的,但它只是依次调用了3个API调用;这与另一个答案相似也就不足为奇了。
  • 在此处也链接 MSDN VB.net 示例项目:code.msdn.microsoft.com/windowsdesktop/…
  • 这可行,但生成的进程将继承其父进程的高度完整性。请参阅我的答案以获取其他代码以启动具有中等完整性的子进程,这是未提升的进程通常具有的。
  • 不确定为什么使用SaferComputeTokenFromLevel 计算的新令牌仍被标记为“提升”。任何从结果过程中再次提升的尝试都将失败。
【解决方案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。

【讨论】:

  • 你不能用这个方法传递参数,可以吗?在 Windows 10 中使用 .BAT 文件也不起作用。
  • @GerardoGrignoli 不,您需要解决这些问题,例如某种形式的 IPC/tcp 套接字、命名管道、使用 args 编写文件/注册表、设置剪贴板文本,甚至创建一个以您想要的(base64 of the)参数命名的 exe 并启动它(并且该 exe 解析/un-b64 它自己的名称并启动实际的 exe)
【解决方案4】:

Raymond Chen 在他的博客中谈到了这一点:

How can I launch an unelevated process from my elevated process and vice versa?

在 GitHub 中搜索此代码的 C# 版本,我在 Microsoft's Node.js 工具 for Visual Studio 存储库中找到了以下实现: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);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-25
    • 2014-08-20
    • 2011-10-07
    • 2021-01-31
    • 2011-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多