如何在 Vista (.NET) 中运行非提升版本

发布于 2024-07-06 10:01:55 字数 158 浏览 9 评论 0原文

我有一个必须以管理员身份运行的应用程序。

该应用程序的一小部分是使用 Process.Start 启动其他应用程序。

启动的应用程序也将以管理员身份运行,但我宁愿看到它们以“普通”用户身份运行。

我该如何做到这一点?

/约翰/

I have an application that I have to run as Administrator.

One small part of that application is to start other applications with Process.Start

The started applications will also be run as administrators, but I'd rather see them run as the 'normal' user.

How do I accomplish that?

/johan/

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

眼眸里的那抹悲凉 2024-07-13 10:01:55

WinSafer API 允许以受限用户、普通用户或提升用户身份启动进程。

示例用法:

CreateSaferProcess(@"calc.exe", "", SaferLevel.NormalUser);

源代码:

//http://odetocode.com/Blogs/scott/archive/2004/10/28/602.aspx
public static void CreateSaferProcess(String fileName, String arguments, SaferLevel saferLevel)
{
   IntPtr saferLevelHandle = IntPtr.Zero;

   //Create a SaferLevel handle to match what was requested
   if (!WinSafer.SaferCreateLevel(
         SaferLevelScope.User, 
         saferLevel, 
         SaferOpen.Open, 
         out saferLevelHandle, 
         IntPtr.Zero))
   {
      throw new Win32Exception(Marshal.GetLastWin32Error());
   }
   try
   {
      //Generate the access token to use, based on the safer level handle.
      IntPtr hToken = IntPtr.Zero;

      if (!WinSafer.SaferComputeTokenFromLevel(
            saferLevelHandle,  // SAFER Level handle
            IntPtr.Zero,       // NULL is current thread token.
            out hToken,        // Target token
            SaferTokenBehaviour.Default,      // No flags
            IntPtr.Zero))      // Reserved
      {
         throw new Win32Exception(Marshal.GetLastWin32Error());
      }
      try
      {
         //Now that we have a security token, we can lauch the process
         //using the standard CreateProcessAsUser API
         STARTUPINFO si = new STARTUPINFO();
         si.cb = Marshal.SizeOf(si);
         si.lpDesktop = String.Empty;

         PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

         // Spin up the new process
         Boolean bResult = Windows.CreateProcessAsUser(
               hToken,
               fileName,
               arguments,
               IntPtr.Zero, //process attributes
               IntPtr.Zero, //thread attributes
               false, //inherit handles
               0, //CREATE_NEW_CONSOLE
               IntPtr.Zero, //environment
               null, //current directory
               ref si, //startup info
               out pi); //process info

         if (!bResult)
            throw new Win32Exception(Marshal.GetLastWin32Error());

         if (pi.hProcess != IntPtr.Zero)
            Windows.CloseHandle(pi.hProcess);

         if (pi.hThread != IntPtr.Zero)
            Windows.CloseHandle(pi.hThread);
      }
      finally
      {
         if (hToken != IntPtr.Zero)
            Windows.CloseHandle(hToken);
      }
   }
   finally
   {
      WinSafer.SaferCloseLevel(saferLevelHandle);
   }
}

P/Invoke 声明:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PInvoke
{
   public class WinSafer
   {
      /// <summary>
      /// The SaferCreateLevel function opens a SAFER_LEVEL_HANDLE.
      /// </summary>
      /// <param name="scopeId">The scope of the level to be created.</param>
      /// <param name="levelId">The level of the handle to be opened.</param>
      /// <param name="openFlags">Must be SaferOpenFlags.Open</param>
      /// <param name="levelHandle">The returned SAFER_LEVEL_HANDLE. When you have finished using the handle, release it by calling the SaferCloseLevel function.</param>
      /// <param name="reserved">This parameter is reserved for future use. IntPtr.Zero</param>
      /// <returns></returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferCreateLevel(SaferLevelScope scopeId, SaferLevel levelId, SaferOpen openFlags,
            out IntPtr levelHandle, IntPtr reserved);

      /// <summary>
      /// The SaferComputeTokenFromLevel function restricts a token using restrictions specified by a SAFER_LEVEL_HANDLE.
      /// </summary>
      /// <param name="levelHandle">SAFER_LEVEL_HANDLE that contains the restrictions to place on the input token. Do not pass handles with a LevelId of SAFER_LEVELID_FULLYTRUSTED or SAFER_LEVELID_DISALLOWED to this function. This is because SAFER_LEVELID_FULLYTRUSTED is unrestricted and SAFER_LEVELID_DISALLOWED does not contain a token.</param>
      /// <param name="inAccessToken">Token to be restricted. If this parameter is NULL, the token of the current thread will be used. If the current thread does not contain a token, the token of the current process is used.</param>
      /// <param name="outAccessToken">The resulting restricted token.</param>
      /// <param name="flags">Specifies the behavior of the method.</param>
      /// <param name="lpReserved">Reserved for future use. This parameter should be set to IntPtr.EmptyParam.</param>
      /// <returns></returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferComputeTokenFromLevel(IntPtr levelHandle, IntPtr inAccessToken,
            out IntPtr outAccessToken, SaferTokenBehaviour flags, IntPtr lpReserved);

      /// <summary>
      /// The SaferCloseLevel function closes a SAFER_LEVEL_HANDLE that was opened by using the SaferIdentifyLevel function or the SaferCreateLevel function.</summary>
      /// <param name="levelHandle">The SAFER_LEVEL_HANDLE to be closed.</param>
      /// <returns>TRUE if the function succeeds; otherwise, FALSE. For extended error information, call GetLastWin32Error.</returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferCloseLevel(IntPtr levelHandle);
   } //class WinSafer

   /// <summary>
   /// Specifies the behaviour of the SaferComputeTokenFromLevel method
   /// </summary>
   public enum SaferTokenBehaviour : uint
   {
      /// <summary></summary>
      Default = 0x0,
      /// <summary>If the OutAccessToken parameter is not more restrictive than the InAccessToken parameter, the OutAccessToken parameter returns NULL.</summary>
      NullIfEqual = 0x1,
      /// <summary></summary>
      CompareOnly = 0x2,
      /// <summary></summary>
      MakeInert = 0x4,
      /// <summary></summary>
      WantFlags = 0x8
   }

   /// <summary>
   /// The level of the handle to be opened.
   /// </summary>
   public enum SaferLevel : uint
   {
      /// <summary>Software will not run, regardless of the user rights of the user.</summary>
      Disallowed = 0,
      /// <summary>Allows programs to execute with access only to resources granted to open well-known groups, blocking access to Administrator and Power User privileges and personally granted rights.</summary>
      Untrusted = 0x1000,
      /// <summary>Software cannot access certain resources, such as cryptographic keys and credentials, regardless of the user rights of the user.</summary>
      Constrained = 0x10000,
      /// <summary>Allows programs to execute as a user that does not have Administrator or Power User user rights. Software can access resources accessible by normal users.</summary>
      NormalUser = 0x20000,
      /// <summary>Software user rights are determined by the user rights of the user.</summary>
      FullyTrusted = 0x40000
   }

   /// <summary>
   /// The scope of the level to be created.
   /// </summary>
   public enum SaferLevelScope : uint
   {
      /// <summary>The created level is scoped by computer.</summary>
      Machine = 1,
      /// <summary>The created level is scoped by user.</summary>
      User = 2
   }

   public enum SaferOpen : uint
   {
      Open = 1
   }
} //namespace PInvoke

The WinSafer API's allow a process to be launched as a limited, normal, or elevated user.

Sample Usage:

CreateSaferProcess(@"calc.exe", "", SaferLevel.NormalUser);

Source code:

//http://odetocode.com/Blogs/scott/archive/2004/10/28/602.aspx
public static void CreateSaferProcess(String fileName, String arguments, SaferLevel saferLevel)
{
   IntPtr saferLevelHandle = IntPtr.Zero;

   //Create a SaferLevel handle to match what was requested
   if (!WinSafer.SaferCreateLevel(
         SaferLevelScope.User, 
         saferLevel, 
         SaferOpen.Open, 
         out saferLevelHandle, 
         IntPtr.Zero))
   {
      throw new Win32Exception(Marshal.GetLastWin32Error());
   }
   try
   {
      //Generate the access token to use, based on the safer level handle.
      IntPtr hToken = IntPtr.Zero;

      if (!WinSafer.SaferComputeTokenFromLevel(
            saferLevelHandle,  // SAFER Level handle
            IntPtr.Zero,       // NULL is current thread token.
            out hToken,        // Target token
            SaferTokenBehaviour.Default,      // No flags
            IntPtr.Zero))      // Reserved
      {
         throw new Win32Exception(Marshal.GetLastWin32Error());
      }
      try
      {
         //Now that we have a security token, we can lauch the process
         //using the standard CreateProcessAsUser API
         STARTUPINFO si = new STARTUPINFO();
         si.cb = Marshal.SizeOf(si);
         si.lpDesktop = String.Empty;

         PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

         // Spin up the new process
         Boolean bResult = Windows.CreateProcessAsUser(
               hToken,
               fileName,
               arguments,
               IntPtr.Zero, //process attributes
               IntPtr.Zero, //thread attributes
               false, //inherit handles
               0, //CREATE_NEW_CONSOLE
               IntPtr.Zero, //environment
               null, //current directory
               ref si, //startup info
               out pi); //process info

         if (!bResult)
            throw new Win32Exception(Marshal.GetLastWin32Error());

         if (pi.hProcess != IntPtr.Zero)
            Windows.CloseHandle(pi.hProcess);

         if (pi.hThread != IntPtr.Zero)
            Windows.CloseHandle(pi.hThread);
      }
      finally
      {
         if (hToken != IntPtr.Zero)
            Windows.CloseHandle(hToken);
      }
   }
   finally
   {
      WinSafer.SaferCloseLevel(saferLevelHandle);
   }
}

P/Invoke declarations:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PInvoke
{
   public class WinSafer
   {
      /// <summary>
      /// The SaferCreateLevel function opens a SAFER_LEVEL_HANDLE.
      /// </summary>
      /// <param name="scopeId">The scope of the level to be created.</param>
      /// <param name="levelId">The level of the handle to be opened.</param>
      /// <param name="openFlags">Must be SaferOpenFlags.Open</param>
      /// <param name="levelHandle">The returned SAFER_LEVEL_HANDLE. When you have finished using the handle, release it by calling the SaferCloseLevel function.</param>
      /// <param name="reserved">This parameter is reserved for future use. IntPtr.Zero</param>
      /// <returns></returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferCreateLevel(SaferLevelScope scopeId, SaferLevel levelId, SaferOpen openFlags,
            out IntPtr levelHandle, IntPtr reserved);

      /// <summary>
      /// The SaferComputeTokenFromLevel function restricts a token using restrictions specified by a SAFER_LEVEL_HANDLE.
      /// </summary>
      /// <param name="levelHandle">SAFER_LEVEL_HANDLE that contains the restrictions to place on the input token. Do not pass handles with a LevelId of SAFER_LEVELID_FULLYTRUSTED or SAFER_LEVELID_DISALLOWED to this function. This is because SAFER_LEVELID_FULLYTRUSTED is unrestricted and SAFER_LEVELID_DISALLOWED does not contain a token.</param>
      /// <param name="inAccessToken">Token to be restricted. If this parameter is NULL, the token of the current thread will be used. If the current thread does not contain a token, the token of the current process is used.</param>
      /// <param name="outAccessToken">The resulting restricted token.</param>
      /// <param name="flags">Specifies the behavior of the method.</param>
      /// <param name="lpReserved">Reserved for future use. This parameter should be set to IntPtr.EmptyParam.</param>
      /// <returns></returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferComputeTokenFromLevel(IntPtr levelHandle, IntPtr inAccessToken,
            out IntPtr outAccessToken, SaferTokenBehaviour flags, IntPtr lpReserved);

      /// <summary>
      /// The SaferCloseLevel function closes a SAFER_LEVEL_HANDLE that was opened by using the SaferIdentifyLevel function or the SaferCreateLevel function.</summary>
      /// <param name="levelHandle">The SAFER_LEVEL_HANDLE to be closed.</param>
      /// <returns>TRUE if the function succeeds; otherwise, FALSE. For extended error information, call GetLastWin32Error.</returns>
      [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
      public static extern bool SaferCloseLevel(IntPtr levelHandle);
   } //class WinSafer

   /// <summary>
   /// Specifies the behaviour of the SaferComputeTokenFromLevel method
   /// </summary>
   public enum SaferTokenBehaviour : uint
   {
      /// <summary></summary>
      Default = 0x0,
      /// <summary>If the OutAccessToken parameter is not more restrictive than the InAccessToken parameter, the OutAccessToken parameter returns NULL.</summary>
      NullIfEqual = 0x1,
      /// <summary></summary>
      CompareOnly = 0x2,
      /// <summary></summary>
      MakeInert = 0x4,
      /// <summary></summary>
      WantFlags = 0x8
   }

   /// <summary>
   /// The level of the handle to be opened.
   /// </summary>
   public enum SaferLevel : uint
   {
      /// <summary>Software will not run, regardless of the user rights of the user.</summary>
      Disallowed = 0,
      /// <summary>Allows programs to execute with access only to resources granted to open well-known groups, blocking access to Administrator and Power User privileges and personally granted rights.</summary>
      Untrusted = 0x1000,
      /// <summary>Software cannot access certain resources, such as cryptographic keys and credentials, regardless of the user rights of the user.</summary>
      Constrained = 0x10000,
      /// <summary>Allows programs to execute as a user that does not have Administrator or Power User user rights. Software can access resources accessible by normal users.</summary>
      NormalUser = 0x20000,
      /// <summary>Software user rights are determined by the user rights of the user.</summary>
      FullyTrusted = 0x40000
   }

   /// <summary>
   /// The scope of the level to be created.
   /// </summary>
   public enum SaferLevelScope : uint
   {
      /// <summary>The created level is scoped by computer.</summary>
      Machine = 1,
      /// <summary>The created level is scoped by user.</summary>
      User = 2
   }

   public enum SaferOpen : uint
   {
      Open = 1
   }
} //namespace PInvoke
若水微香 2024-07-13 10:01:55

来自:http://go.microsoft.com/fwlink/?LinkId=81232

一个常见问题是如何
从以下位置启动未提升的应用程序
一个提升的过程,或者更多
从根本上讲,我如何启动
使用我未提升的令牌进行处理
一旦我跑高了。 自从
没有直接的方法可以做到这一点,
通常可以通过以下方式避免这种情况
启动原始应用程序
标准用户并且只提升那些
应用程序的部分
需要管理权限。 这
总有一个不高的方式
可用于启动的进程
额外的应用程序作为
当前登录桌面用户。
然而,有时,升高的
进程需要得到另一个
应用程序运行未提升。 这
可以通过使用任务来完成
Windows Vista 中的调度程序。 这
提升的进程可以注册任务
以当前登录的身份运行
桌面用户。

以下是如何安排未提升流程的示例(同样来自同一链接)

//---------------------------------------------------------------------
//  This file is part of the Microsoft .NET Framework SDK Code Samples.
// 
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// 
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------

/****************************************************************************
* Main.cpp - Sample application for Task Scheduler V2 COMAPI                * Component: Task Scheduler                          
* Copyright (c) 2002 - 2003, Microsoft Corporation 
* This sample creates a task to that launches as the currently logged on deskup user. The task launches as soon as it is registered.                                                             *
****************************************************************************/
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <comdef.h>
#include <comutil.h>
//Include Task header files - Included in Windows Vista Beta-2 SDK from MSDN
#include <taskschd.h>
#include <conio.h>
#include <iostream>
#include <time.h>

using namespace std;

#define CLEANUP \
pRootFolder->Release();\
        pTask->Release();\
        CoUninitialize();

HRESULT CreateMyTask(LPCWSTR, wstring);

void __cdecl wmain(int argc, wchar_t** argv)
{
wstring wstrExecutablePath;
WCHAR taskName[20];
HRESULT result;

if( argc < 2 )
{
printf("\nUsage: LaunchApp yourapp.exe" );
return;
}

// Pick random number for task name
srand((unsigned int) time(NULL));
wsprintf((LPWSTR)taskName, L"Launch %d", rand());

wstrExecutablePath = argv[1];

result = CreateMyTask(taskName, wstrExecutablePath);
printf("\nReturn status:%d\n", result);

}
HRESULT CreateMyTask(LPCWSTR wszTaskName, wstring wstrExecutablePath)
{
    //  ------------------------------------------------------
    //  Initialize COM.
TASK_STATE taskState;
int i;
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if( FAILED(hr) )
    {
        printf("\nCoInitializeEx failed: %x", hr );
        return 1;
    }

    //  Set general COM security levels.
    hr = CoInitializeSecurity(
        NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        0,
        NULL);

    if( FAILED(hr) )
    {
        printf("\nCoInitializeSecurity failed: %x", hr );
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Create an instance of the Task Service. 
    ITaskService *pService = NULL;
    hr = CoCreateInstance( CLSID_TaskScheduler,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_ITaskService,
                           (void**)&pService );  
    if (FAILED(hr))
    {
        printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
        CoUninitialize();
        return 1;
    }

    //  Connect to the task service.
    hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
    if( FAILED(hr) )
    {
        printf("ITaskService::Connect failed: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Get the pointer to the root task folder.  This folder will hold the
    //  new task that is registered.
    ITaskFolder *pRootFolder = NULL;
    hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
    if( FAILED(hr) )
    {
        printf("Cannot get Root Folder pointer: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }

    //  Check if the same task already exists. If the same task exists, remove it.
    hr = pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0  );

    //  Create the task builder object to create the task.
    ITaskDefinition *pTask = NULL;
    hr = pService->NewTask( 0, &pTask );

    pService->Release();  // COM clean up.  Pointer is no longer used.
    if (FAILED(hr))
    {
        printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
        pRootFolder->Release();
        CoUninitialize();
        return 1;
    }


    //  ------------------------------------------------------
    //  Get the trigger collection to insert the registration trigger.
    ITriggerCollection *pTriggerCollection = NULL;
    hr = pTask->get_Triggers( &pTriggerCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get trigger collection: %x", hr );
  CLEANUP
        return 1;
    }

    //  Add the registration trigger to the task.
    ITrigger *pTrigger = NULL;

    hr = pTriggerCollection->Create( TASK_TRIGGER_REGISTRATION, &pTrigger );     
    pTriggerCollection->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\nCannot add registration trigger to the Task %x", hr );
        CLEANUP
        return 1;
    }
    pTrigger->Release();

    //  ------------------------------------------------------
    //  Add an Action to the task.     
    IExecAction *pExecAction = NULL;
    IActionCollection *pActionCollection = NULL;

    //  Get the task action collection pointer.
    hr = pTask->get_Actions( &pActionCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get Task collection pointer: %x", hr );
        CLEANUP
        return 1;
    }

    //  Create the action, specifying that it is an executable action.
    IAction *pAction = NULL;
    hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
    pActionCollection->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\npActionCollection->Create failed: %x", hr );
        CLEANUP
        return 1;
    }

    hr = pAction->QueryInterface( IID_IExecAction, (void**) &pExecAction );
    pAction->Release();
    if( FAILED(hr) )
    {
        printf("\npAction->QueryInterface failed: %x", hr );
        CLEANUP
        return 1;
    }

    //  Set the path of the executable to the user supplied executable.
   hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) );  

    if( FAILED(hr) )
    {
        printf("\nCannot set path of executable: %x", hr );
        pExecAction->Release();
        CLEANUP
        return 1;
    }
    hr = pExecAction->put_Arguments( _bstr_t( L"" ) );  

   if( FAILED(hr) )
    {
        printf("\nCannot set arguments of executable: %x", hr );
        pExecAction->Release();
        CLEANUP
        return 1;
    }

    //  ------------------------------------------------------
    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;
    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t( wszTaskName ),
            pTask,
      TASK_CREATE, 
_variant_t(_bstr_t( L"S-1-5-32-545")),//Well Known SID for \\Builtin\Users group
_variant_t(), 
TASK_LOGON_GROUP,
            _variant_t(L""),
            &pRegisteredTask);
    if( FAILED(hr) )
    {
        printf("\nError saving the Task : %x", hr );
        CLEANUP
        return 1;
    }
    printf("\n Success! Task successfully registered. " );
    for (i=0; i<100; i++)//give 10 seconds for the task to start
{
pRegisteredTask->get_State(&taskState);
if (taskState == TASK_STATE_RUNNING)
{
printf("\nTask is running\n");
break;
}
Sleep(100);
}
if (i>= 100) printf("Task didn't start\n");

    //Delete the task when done
    hr = pRootFolder->DeleteTask(
            _bstr_t( wszTaskName ),
            NULL);
    if( FAILED(hr) )
    {
        printf("\nError deleting the Task : %x", hr );
        CLEANUP
        return 1;
    }

    printf("\n Success! Task successfully deleted. " );

//  Clean up.
    CLEANUP
    CoUninitialize();
    return 0;
}

From: http://go.microsoft.com/fwlink/?LinkId=81232

A frequently asked question is how to
launch an un-elevated application from
an elevated process, or more
fundamentally, how to I launch a
process using my un-elevated token
once I’m running elevated. Since
there is no direct way to do this, the
situation can usually be avoided by
launching the original application as
standard user and only elevating those
portions of the application that
require administrative rights. This
way there is always a non-elevated
process that can be used to launch
additional applications as the
currently logged on desktop user.
Sometimes, however, an elevated
process needs to get another
application running un-elevated. This
can be accomplished by using the task
scheduler within Windows Vista. The
elevated process can register a task
to run as the currently logged on
desktop user.

Here is an example of how to schedule the un-elevated process (again from the same link)

//---------------------------------------------------------------------
//  This file is part of the Microsoft .NET Framework SDK Code Samples.
// 
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// 
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------

/****************************************************************************
* Main.cpp - Sample application for Task Scheduler V2 COMAPI                * Component: Task Scheduler                          
* Copyright (c) 2002 - 2003, Microsoft Corporation 
* This sample creates a task to that launches as the currently logged on deskup user. The task launches as soon as it is registered.                                                             *
****************************************************************************/
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <comdef.h>
#include <comutil.h>
//Include Task header files - Included in Windows Vista Beta-2 SDK from MSDN
#include <taskschd.h>
#include <conio.h>
#include <iostream>
#include <time.h>

using namespace std;

#define CLEANUP \
pRootFolder->Release();\
        pTask->Release();\
        CoUninitialize();

HRESULT CreateMyTask(LPCWSTR, wstring);

void __cdecl wmain(int argc, wchar_t** argv)
{
wstring wstrExecutablePath;
WCHAR taskName[20];
HRESULT result;

if( argc < 2 )
{
printf("\nUsage: LaunchApp yourapp.exe" );
return;
}

// Pick random number for task name
srand((unsigned int) time(NULL));
wsprintf((LPWSTR)taskName, L"Launch %d", rand());

wstrExecutablePath = argv[1];

result = CreateMyTask(taskName, wstrExecutablePath);
printf("\nReturn status:%d\n", result);

}
HRESULT CreateMyTask(LPCWSTR wszTaskName, wstring wstrExecutablePath)
{
    //  ------------------------------------------------------
    //  Initialize COM.
TASK_STATE taskState;
int i;
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if( FAILED(hr) )
    {
        printf("\nCoInitializeEx failed: %x", hr );
        return 1;
    }

    //  Set general COM security levels.
    hr = CoInitializeSecurity(
        NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        0,
        NULL);

    if( FAILED(hr) )
    {
        printf("\nCoInitializeSecurity failed: %x", hr );
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Create an instance of the Task Service. 
    ITaskService *pService = NULL;
    hr = CoCreateInstance( CLSID_TaskScheduler,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_ITaskService,
                           (void**)&pService );  
    if (FAILED(hr))
    {
        printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
        CoUninitialize();
        return 1;
    }

    //  Connect to the task service.
    hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
    if( FAILED(hr) )
    {
        printf("ITaskService::Connect failed: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }

    //  ------------------------------------------------------
    //  Get the pointer to the root task folder.  This folder will hold the
    //  new task that is registered.
    ITaskFolder *pRootFolder = NULL;
    hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
    if( FAILED(hr) )
    {
        printf("Cannot get Root Folder pointer: %x", hr );
        pService->Release();
        CoUninitialize();
        return 1;
    }

    //  Check if the same task already exists. If the same task exists, remove it.
    hr = pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0  );

    //  Create the task builder object to create the task.
    ITaskDefinition *pTask = NULL;
    hr = pService->NewTask( 0, &pTask );

    pService->Release();  // COM clean up.  Pointer is no longer used.
    if (FAILED(hr))
    {
        printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
        pRootFolder->Release();
        CoUninitialize();
        return 1;
    }


    //  ------------------------------------------------------
    //  Get the trigger collection to insert the registration trigger.
    ITriggerCollection *pTriggerCollection = NULL;
    hr = pTask->get_Triggers( &pTriggerCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get trigger collection: %x", hr );
  CLEANUP
        return 1;
    }

    //  Add the registration trigger to the task.
    ITrigger *pTrigger = NULL;

    hr = pTriggerCollection->Create( TASK_TRIGGER_REGISTRATION, &pTrigger );     
    pTriggerCollection->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\nCannot add registration trigger to the Task %x", hr );
        CLEANUP
        return 1;
    }
    pTrigger->Release();

    //  ------------------------------------------------------
    //  Add an Action to the task.     
    IExecAction *pExecAction = NULL;
    IActionCollection *pActionCollection = NULL;

    //  Get the task action collection pointer.
    hr = pTask->get_Actions( &pActionCollection );
    if( FAILED(hr) )
    {
        printf("\nCannot get Task collection pointer: %x", hr );
        CLEANUP
        return 1;
    }

    //  Create the action, specifying that it is an executable action.
    IAction *pAction = NULL;
    hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
    pActionCollection->Release();  // COM clean up.  Pointer is no longer used.
    if( FAILED(hr) )
    {
        printf("\npActionCollection->Create failed: %x", hr );
        CLEANUP
        return 1;
    }

    hr = pAction->QueryInterface( IID_IExecAction, (void**) &pExecAction );
    pAction->Release();
    if( FAILED(hr) )
    {
        printf("\npAction->QueryInterface failed: %x", hr );
        CLEANUP
        return 1;
    }

    //  Set the path of the executable to the user supplied executable.
   hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) );  

    if( FAILED(hr) )
    {
        printf("\nCannot set path of executable: %x", hr );
        pExecAction->Release();
        CLEANUP
        return 1;
    }
    hr = pExecAction->put_Arguments( _bstr_t( L"" ) );  

   if( FAILED(hr) )
    {
        printf("\nCannot set arguments of executable: %x", hr );
        pExecAction->Release();
        CLEANUP
        return 1;
    }

    //  ------------------------------------------------------
    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;
    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t( wszTaskName ),
            pTask,
      TASK_CREATE, 
_variant_t(_bstr_t( L"S-1-5-32-545")),//Well Known SID for \\Builtin\Users group
_variant_t(), 
TASK_LOGON_GROUP,
            _variant_t(L""),
            &pRegisteredTask);
    if( FAILED(hr) )
    {
        printf("\nError saving the Task : %x", hr );
        CLEANUP
        return 1;
    }
    printf("\n Success! Task successfully registered. " );
    for (i=0; i<100; i++)//give 10 seconds for the task to start
{
pRegisteredTask->get_State(&taskState);
if (taskState == TASK_STATE_RUNNING)
{
printf("\nTask is running\n");
break;
}
Sleep(100);
}
if (i>= 100) printf("Task didn't start\n");

    //Delete the task when done
    hr = pRootFolder->DeleteTask(
            _bstr_t( wszTaskName ),
            NULL);
    if( FAILED(hr) )
    {
        printf("\nError deleting the Task : %x", hr );
        CLEANUP
        return 1;
    }

    printf("\n Success! Task successfully deleted. " );

//  Clean up.
    CLEANUP
    CoUninitialize();
    return 0;
}
醉南桥 2024-07-13 10:01:55

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

如何从我的提升进程和副进程中启动未提升进程反之亦然?

在 GitHub 中搜索此代码的 C# 版本,我在 Microsoft 的 Visual Studio Node.js 工具 存储库中找到了以下实现:SystemUtilities.csExecuteProcessUnElevated 函数)。

以防万一文件消失,文件内容如下:

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

Raymond Chen addressed this in his blog:

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

Searching in GitHub for a C# version of this code, I found the following implementation in Microsoft's Node.js tools for Visual Studio repository: SystemUtilities.cs (the ExecuteProcessUnElevated function).

Just in case the file disappears, here's the file's contents:

// 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);
        }
    }
}
零度℉ 2024-07-13 10:01:55

我有同样的要求,我找到了使用 Windows 任务计划程序服务的解决方案。

因此,首先添加 Task Scheduler Managed Wrapper 项目库 并使用此代码创建任务,将其配置为以受限用户身份运行 (td.Principal. RunLevel = TaskRunLevel.LUA;),注册任务,运行任务,完成后删除任务。

// Get the service on the local machine
using (var ts = new TaskService())
{
     const string taskName = "foo";

     // Create a new task definition and assign properties
     var td = ts.NewTask();
     td.RegistrationInfo.Description = "start foo.exe as limited user";

     // Create an action that will launch foo.exe, with argument bar in workingdir C:\\
     td.Actions.Add(new ExecAction("C:\\foo.exe", "bar", "C:\\"));

     td.Settings.Priority = ProcessPriorityClass.Normal;

     // run with limited token
     td.Principal.RunLevel = TaskRunLevel.LUA;

     td.Settings.AllowDemandStart = true;

     td.Settings.DisallowStartIfOnBatteries = false;

     td.Settings.StopIfGoingOnBatteries = false;

     // Register the task in the root folder
     var ret = ts.RootFolder.RegisterTaskDefinition(taskName, td);

     var fooTask = ts.FindTask(taskName, true);
     if (null != fooTask )
     {
         if (fooTask.Enabled)
         {
             fooTask.Run();

             Thread.Sleep(TimeSpan.FromSeconds(1));

             // find process and wait for Exit
             var processlist = Process.GetProcesses();

             foreach(var theprocess in processlist)
             {
                 if (theprocess.ProcessName != "foo")
                    continue;

                 theprocess.WaitForExit();
                 break;
             }
         }
    }

    // Remove the task we just created
    ts.RootFolder.DeleteTask(taskName);
}

I had the same requirement and I come to the solution to use the task scheduler service from Windows.

So, first add the Task Scheduler Managed Wrapper library to your project and use this code to create a task, configure it to run as limited user (td.Principal.RunLevel = TaskRunLevel.LUA;), register the task, run the task and after finish, delete the task.

// Get the service on the local machine
using (var ts = new TaskService())
{
     const string taskName = "foo";

     // Create a new task definition and assign properties
     var td = ts.NewTask();
     td.RegistrationInfo.Description = "start foo.exe as limited user";

     // Create an action that will launch foo.exe, with argument bar in workingdir C:\\
     td.Actions.Add(new ExecAction("C:\\foo.exe", "bar", "C:\\"));

     td.Settings.Priority = ProcessPriorityClass.Normal;

     // run with limited token
     td.Principal.RunLevel = TaskRunLevel.LUA;

     td.Settings.AllowDemandStart = true;

     td.Settings.DisallowStartIfOnBatteries = false;

     td.Settings.StopIfGoingOnBatteries = false;

     // Register the task in the root folder
     var ret = ts.RootFolder.RegisterTaskDefinition(taskName, td);

     var fooTask = ts.FindTask(taskName, true);
     if (null != fooTask )
     {
         if (fooTask.Enabled)
         {
             fooTask.Run();

             Thread.Sleep(TimeSpan.FromSeconds(1));

             // find process and wait for Exit
             var processlist = Process.GetProcesses();

             foreach(var theprocess in processlist)
             {
                 if (theprocess.ProcessName != "foo")
                    continue;

                 theprocess.WaitForExit();
                 break;
             }
         }
    }

    // Remove the task we just created
    ts.RootFolder.DeleteTask(taskName);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文