views:

1869

answers:

2

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/

+5  A: 

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;
}
Greg Dean
The linked article moved, and no longer contains that quote. Googling for the quote finds the original article, which mentions the sample code in the "references" section of the same article. Expectedly, there is no code sample in the article. Yet another reason to put answers on Stackoverflow, not links to ansers.
Ian Boyd
Tell Jeff Atwood
Greg Dean
+6  A: 

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
Ian Boyd
IIRC, a process started with a Safer token still runs @ High IL if the parent process was High IL so you may have a deny admin SID in the token, but its not a normal user token
Anders
i suppose someone should peek into iexplore and see how they relaunch themselves as restricted.
Ian Boyd
i just spied on an IE startup. It relaunches IE using CreateProcessAsUserW(), so...
Ian Boyd