Rick Strahl's Weblog
Rick Strahl's FoxPro and Web Connection Weblog
White Papers | Products | Message Board | News |

Executing an EXE with Run As Administrator Dialog


August 29, 2007 •

With Windows Vista there are a number of things that require potential elevation of rights for applications in order to perform certain administrative tasks. For example in Help Builder the auto-updater requires that a new updated EXE can be written to the Program Files installation folder when an update becomes available.

 

Although this is a more acute issue with Vista, this also applies to XP if you are not running as an Administrator.

 

Two things make this process easier: A way to detect whether the user has Admin capabilities and a way to restart an application by prompting with the Run As Administrator dialog.

 

In Help Builder I have code that basically does the following (after checking for whether updates are actually available):

 

IF !IsAdmin()

   GoUrl( GetAppStartPath(), "RUNAS")

   QUIT

ENDIF

 

To force a restart. And it works well.

 

The first thing is figuring out whether you are an admin. There are several API calls available for this and this one’s the easiest and most widely available on Windows:

 

************************************************************************

* wwUtils ::  IsAdmin

****************************************

***  Function: Determines whether user is an admin user

***    Assume:

***      Pass:

***    Return:

************************************************************************

FUNCTION IsAdmin()

LOCAL loAPI, lcVal

 

DECLARE INTEGER IsUserAnAdmin IN Shell32

TRY

      lnResult = IsUserAnAdmin()

CATCH

      *** OLD OLD Version of Windows assume .T.

      lnResult = 1

ENDTRY

IF lnResult = 0

   RETURN .F.

ENDIF

 

RETURN .T. 

ENDFUNC

*  wwUtils ::  IsAdmin

 

Note although the docs say it works with Windows 2000 forward, the API call doesn’t exist in the original non-SP’d version of Win2000. The above API was introduced with one of the service packs. However, if the box is that old and unpatched most likely security is not going to be an issue so it just assumes the user is an admin.

 

Next you can use your application to figure out exactly what EXE what used to launch the application. I like to use a generic routine that figures out what actually launched an application:

 

************************************************************************

FUNCTION GetAppStartPath

*********************************

***  Function: Returns the FoxPro start path

***            of the *APPLICATION*

***            under all startmodes supported by VFP.

***            Returns the path of the starting EXE,

***            DLL, APP, PRG/FXP

***    Return: Path as a string with trailing "\"

************************************************************************

 

DO CASE

   *** VFP 6 and later provides ServerName property for COM servers EXE/DLL/MTDLL

   CASE INLIST(Application.StartMode,2,3,5)

         lcPath = JustPath(Application.ServerName)

 

   *** Interactive

   CASE (Application.StartMode) = 0

         lcPath = SYS(5) + CURDIR()

        

   *** Active Document

   CASE ATC(".APP",SYS(16,0)) > 0

       lcPath = JustPath(SYS(16,0))

 

  *** Standalone EXE or VFP Development

  OTHERWISE

       lcPath = JustPath(SYS(16,0))

       IF ATC("PROCEDURE",lcPath) > 0

         lcPath = SUBSTR(lcPath,RAT(":",lcPath)-1)

       ENDIF

ENDCASE

 

RETURN AddBs(lcPath)

* EOF GetAppStartPath     

 

Finally you can use ShellExecute with an action of RUNAS to force the Administrative rights dialog to pop up. Again I use a wrapper method:

 

****************************************************

FUNCTION GoUrl

******************

***    Author: Rick Strahl

***            (c) West Wind Technologies, 1996

***   Contact: rstrahl@west-wind.com

***  Modified: 03/14/96

***  Function: Starts associated Web Browser

***            and goes to the specified URL.

***            If Browser is already open it

***            reloads the page.

***    Assume: Works only on Win95 and NT 4.0

***      Pass: tcUrl  - The URL of the site or

***                     HTML page to bring up

***                     in the Browser

***    Return: 2  - Bad Association (invalid URL)

***            31 - No application association

***            29 - Failure to load application

***            30 - Application is busy

***

***            Values over 32 indicate success

***            and return an instance handle for

***            the application started (the browser)

****************************************************

LPARAMETERS tcUrl, tcAction, tcDirectory, tcParms

 

IF EMPTY(tcUrl)

   RETURN -1

ENDIF

IF EMPTY(tcAction)

   tcAction = "OPEN"

ENDIF

IF EMPTY(tcDirectory)

   tcDirectory = SYS(2023)

ENDIF

 

DECLARE INTEGER ShellExecute ;

    IN SHELL32.dll ;

    INTEGER nWinHandle,;

    STRING cOperation,;

    STRING cFileName,;

    STRING cParameters,;

    STRING cDirectory,;

    INTEGER nShowWindow

IF EMPTY(tcParms)

   tcParms = ""

ENDIF

 

DECLARE INTEGER FindWindow ;

   IN WIN32API ;

   STRING cNull,STRING cWinName

 

RETURN ShellExecute(FindWindow(0,_SCREEN.caption),;

                    tcAction,tcUrl,;

                    tcParms,tcDirectory,1)

 

 

I’ve used this approach in several applications now and it works well. I use self-updating code in several applications and this fix – while not perfect – is acceptable. It’s reasonable IMHO to prompt people to restart with admin rights for an operation that copies new code onto your machine.

 

Just for fun here’s the whole Help Builder update routine. It uses the wwCodeUpdate component I described in an article some time ago.

 

************************************************************************

PROCEDURE CheckForNewVersion

****************************

***  Modified: 05/30/00

***  Function: Checks for a new version every few days

***      Pass: lnDays

***    Return: nothing

*************************************************************************

LPARAMETER lnDays, loConfig, llForceResult

LOCAL  loUpdate, loIP

 

IF VARTYPE(loConfig) #   "O"

   loConfig = CREATE("wwHelpConfig")

ENDIF

 

IF EMPTY(lnDays)

   lnDays = loConfig.nVersionCheckFrequency

ENDIF

 

*** Load the configuration settings from INI file

loConfig.Load()

IF !llForceResult AND loConfig.dLastVersionCheck > DATE() - lnDays

   RETURN

ENDIF

 

*** Create HTTP Component and make sure server is accessible

*** wait for no more than 2 seconds

loIP = CREATE("wwHTTP")

IF !loIP.HTTPPing(3,HTTP_SERVER,WWHELP_DOWNLOADCHECK_LINK_RELATIVEURL)

   IF llForceResult

        MESSAGEBOX("Unable to connect to " + HTTP_SERVER,48,WWHELP_APPNAME)

   ENDIF

   RETURN

ENDIF

 

*** Now set up to download the servers version info file (XML)

loUpdate=CREATEOBJECT("wwCodeUpdate")

loUpdate.cVersionUrl = "http://"+HTTP_SERVER + WWHELP_DOWNLOADCHECK_LINK_RELATIVEURL

loUpdate.cVersionType = "N"

loUpdate.cEXEFile = "wwhelp.exe"

 

*** Retrieve the version and update info from the server

lnVersion = loUpdate.GetVersionInfo()

IF EMPTY(lnVersion)

   IF llForceResult

        MESSAGEBOX("There's been an error retrieving the version info.",48,WWHELP_APPNAME)

   ENDIF

   RETURN

ENDIF

 

*** Update the version check date in the INI (not saved yet)

loConfig.dLastVersionCheck = DATE()

loConfig.Save()

 

lnCurVersion = WWHELP_VERSION

 

IF !loUpdate.IsUpToDate(WWHELP_VERSION)

 

   IF MessageBox(loUpdate.cUserMessage + CHR(13) +;

                 "Would you like to download and install it now?",;

                 32+4,;

                 "HTML Help Builder Update Notice") = 6               

 

*** Admin Prompt

   IF !IsAdmin()

TEXT TO RunAsAdminNote TEXTMERGE NOSHOW

<body style="font-family:verdana;font-size:10pt;top-margin:0" scroll="no">

<table><tr><td>

<img src="<< SYS(5)+CURDIR() >>bmp\images\alertIcon.gif" align="left" hspace="5">

<td><td align="center" valign="center">

<b style="font-size:12pt;color:maroon">Administrative Permissions Required</b>

</td><tr></table>

<hr style="color:darkblue;height:1pt;">

<p>

In order to download and install this Help Builder update you need to be

logged in as an Administrator.

<p>

You are currently <b>not logged</b> in as an Administrator.

<p>

To perform the Help Builder update, please exit the application

and restart it by using the Run As option and choosing a user

account that has Administrative priviliges from the

shortcut menu or from Windows Explorer (wwhelp.exe).

<p>

<b>What would you like to do?</b>

<ul><li> <b>Exit</b> Help Builder and show Help Builder install directory

<li> Just <b>Return</b> back to Help Builder

<li> Go ahead and <b>Try anyway</b> to update

<li> Get more information in the <b>Help</b> file

</ul>

</body>

ENDTEXT

 

      lcResult = MessageDisplay(RunAsAdminNote,"Help Builder Update",;

                                       "Exit,Return,Try anyway,Help",450,420,VARTYPE(goHelp) = "U")                       

      DO CASE

        CASE lcResult = 2 OR lcResult < 1

            RETURN

        CASE lcResult = 1

            *** Update the version check date so it pops back up

            loConfig.dLastVersionCheck = DATE() - 60

            loConfig.Save()

 

          IF (VARTYPE(GoHelp) = "O")

                  GoUrl(GetAppStartPath(),"RUNAS")

            ENDIF

            QUIT

        CASE lcResult = 4

          HELP ID 875

          RETURN

      ENDCASE

   ENDIF && IsAdmin

*** End Admin Prompt

 

 

         IF !loUpdate.DownloadUpdate(.F.)

            MessageBox(loUpdate.cErrorMsg,48,"Download Error") 

         ELSE

              *** Also try to download Updated CodeUpdate.exe

              lcFile = loIP.HttpGet(WWHELP_DOWNLOADCODEUPDATE_LINK)

              IF loIP.nError = 0 AND !EMPTY(lcFile)

                   FILE2Var(GetAppStartPath() + "codeupdate.exe",lcFile)               

              ENDIF

              

           CLEAR EVENTS

 

         *** Make sure we release

           goHelp = .f.

        

         *** Shut down Help Builder

            IF TYPE("THISFORM") = "O"

           RELEASE THISFORM

            ENDIF

        

         *** Erase the Menu file

         ERASE "bmp\main.tb"

                  

         *** Make sure Help Builder Add-In isn't still loaded

         IF FILE("vsAddin\HelpBuildervsAddin.dll")

            lnHandle = FOPEN("vsAddin\HelpBuildervsAddin.dll",2)

            IF lnHandle > -1

               FCLOSE(lnHandle)

            ELSE

               MESSAGEBOX("Visual Studio .NET and the Help Builder add-in are still running."+ CHR(13) +;

                          "Please shut down all instances of VS.NET before continuing.",48,;

                          WWHELP_APPNAME)

            ENDIF                      

         ENDIF

        

        *** Shuts down Help Builder, unzips the update file and then restarts

        loUpdate.RunUpdateExe(SYS(5) + ADDBS(CURDIR()) + "CodeUpdate.exe",;

                               ["wwHelp.exe","] + ;

                               SYS(5) + ADDBS(CURDIR()) +;

                               [codeupdate\wwHelp400_Update.exe /auto ] + SYS(5) + CURDIR() + [",] + ;

                               ["West Wind HTML Help Builder"])

      ENDIF

   ENDIF

ELSE

   IF llForceResult

      MESSAGEBOX("Your version of Help Builder is up to date",64,WWHELP_APPNAME)

   ENDIF

ENDIF

 

RETURN

* eof CheckfornewVersion

 

This approach uses a separate EXE to handle the update itself. So the main app runs as wwhelp.exe and it downloads the file and uses an external EXE only for the update.

 

Notice that if you’re running as an administrator any child processes launched from the main EXE also end up running under Admin priviliges so when the updater fires it properly updates the files.

Posted in:

Feedback for this Weblog Entry