Some advice how to elevate batch to admin rights
Posted: Wed Feb 13, 2013 1:38 am
I could use some help from one of my favorite software mentors. The below is a mess, so forgive my rambling. Some of my issues might also be due to trying to use a batch command I've never used before, "SCHTASKS", which I'll get to later. I'm trying to learn how to automatically deal, via batch, with the problems of needing to have Admin permissions to do certain tasks while keeping in mind:
* UAC is enabled or disabled
* Dealing with 32-bit and 64-bit
* Dealing with XP requirements vs Vista and onward, ie NT5 vs NT6, Handle at least XP, 2003, Vista, Win7, 2008, Win8, 2012 All versions Home to Ultimate to Server
* The possibility of dealing with batch parameters
* Handling file systems in different languages, ie dealing with special characters
* Ideally, code should work both during OS install and on a running system
* Depend on as few external files or programs as possible
* Be location independent and preferably be able to run correctly from CD/DVD, ie non-writeable source
* Be as small, fast, and unobtrusive as possible
Individually, most of the requirements are easy to deal with, but I've not seen anything that deals with them all at once. I personally have no immediate real need for such code, it started out just trying to help someone out improving a batch they already found. But you know me. If I'm going to write code, I will try to make it flexible, bulletproof, and all powerful! MWUAHAHAH! :cough:, :cough: Excuse me, I digress. Sorry. LOL It began with this post http://www.wincert.net/forum/topic/10458-everything-search-engine-v130632b/?p=90706.
[If there are better alternatives to doing this via batch I would like to know about them, but I would like to understand how to do this via batch if at all possible. I think I can understand the underlying issues better that way.]
I found many references to the Admin Access Prompt for Batch files issue. It seems the accepted process is to test to see if you already have Admin access, and if you don't, to relaunch the batch requesting Admin access and requiring the user to approve this. ( I would think it would be great if Admin rights could be obtained without requiring the user's approval, but I guess that would be a security issue. I also seem to never run into the need for this since I operate as an Admin with UAC disabled, but if I'm going to try and improve this batch, it would be best if it can operate correctly with UAC enabled. ) I'm also not totally sure how best to fit XP into this. Do you ignore all this if the OS is XP or does XP have to deal with the issue as well? It's been too long since I used XP and A just don't remember. I'll assume XP has to deal with it as well in this discussion.
So, to begin, assuming you have a batch that does a task, but it requires Admin access and you want to adapt it to work correctly with UAC enabled, what then? I found many examples of code that you can insert at the beginning of your batch code that is supposed to do the test and relaunch as described above.
https://sites.google.com/site/eneerge/scripts/batchgotadmin - BatchGotAdmin International-Fix Code:
NOTES:
* This is supposed to deal with the special character issue:
* It also does not depend on trying to write a dummy file to a restricted area as a test, nor does it use the old trick "AT > NUL" since AT depends on a service that is often disabled.
* But this method does not forward arguments
This is essentially what Rob van der Woude also lists [url]http://www.robvanderwoude.com/clevertricks.php[/url:
I was having problems testing my code, because I couldn't reliably keep the relaunched cmd window open during code execution so I could use 'echo-pause' statements to see what was going on in the relaunched batch. Though similar to the above code, I didn't care for the exact method shown here http://jagaroth.livejournal.com/63875.html:
but it did give me the idea of spawning my relaunched batch in a specified "cmd.exe". This did wonders in stabilizing my test environment such that my relaunched code was always visible.
This next link - http://stackoverflow.com/questions/1894967/how-to-request-administrator-access-inside-a-batch-file:
gave me an idea how to pass the batch parameters to the relaunched program, but again it just wasn't reliable for me. After playing around for a while, I came up with this:
which was reliable. My very simplified testing on Win7 x64 with UAC disabled indicates that this reliably handles relaunching a batch program with or without parameters even if some or all of those parameters are enclosed in quotes. It even properly handles extra or empty quotes, such as Param-6 or Param-2 below:
Improper parameters such as ""Param x"" will be handled the same way that they are usually handled, which is not the way you probably want them to be handled. The embedded space will break the single parameter into two parameters as far as the batch processor is concerned. I have code available that I could use to "correct" such a problem, but did not include it since I considered it beyond the scope of this exercise.
Note that I also picked up a tip along the way to specify cspript to launch the VBScript in case the user did not have .vbs files associated with a proper script engine.
An even better test might be "CALL NET SESSION >nul 2>&1" as shown here: http://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights/4054937#4054937 (NOTE: Even though "NET SESSION" is proven to be a valid test for Admin rights on XP, both of the next two coding examples skip the relaunch if XP is the detected OS. Why??):
along with experimental proof by Ben Hooper that this test for admin rights works on XP - Win8:
Then there was a discussion that began here - http://ss64.org/viewtopic.php?id=1491 suggesting that the batch command "OPENFILES" is the best test for admin rights and is the least likely to get changed by MS as the OS evolves (It wasn't "proven", but I assume that "OPENFILES" is just as valid a test as "CALL NET SESSION >nul 2>&1" was proven above.):
and that cumulated with the example given here http://ss64.com/vb/syntax-elevate.html:
NOTE: They brought up the point that when a script is run with elevated permissions, several aspects of the user environment may change: The current directory, the current TEMP folder and any mapped drives will be disconnected. So the script saves the current drive mappings and re-maps them once the session has been elevated.
I then also considered the possibility of the original batch being called by something that did not expect to have control returned until the batch was finished. Even launching the elevated batch via -- START "Relaunch" /WAIT cscript "%_tempvbs%" //nologo -- doesn't really do any good since the VBScript launches the elevated batch and then closes, releasing the calling batch from the "/WAIT" condition before the elevated batch is finished. So since I had to create the temporary batch file anyway, I figured I would use that as my flag file and not delete that via the elevated batch until it was done and I added a wait loop in the calling batch to wait for it. That also means that if the elevated batch hangs or does not complete normally, that the file will not be deleted and the calling batch will wait forever until canceled by the user. I wish I had a better idea, but that was the best I could come up with.
So taking all I learned above, I combined all of that into the following:
This code seems to work fine on my Win7 x64 Ultimate with UAC disabled, but then in that situation not much is being done, is it? So I forced it to go though the relaunch by REM'ing out the line -- (OPENFILES >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:gotAdmin) -- and the batch relaunched with all parameters being passed along appropriately. I then tried it on my wife's laptop which has Win7 x64 Home Premium with UAC enabled. With an "ECHO--PAUSE" statement in the "Your Batch HERE" section it tested fine. The batch was relaunched, the UAC prompt was displayed and once approved the "echo" statement displayed. Once I then pressed "Any Key" the window closed and everything closed and cleaned up correctly. So far so good.
But I need to know if it's appropriate to treat XP and 2003, ie NT5, the same way or should I test for NT5 at the beginning and skip straight to :gotAdmin if NT5 is detected? I guess I should skip the relaunch if trying to relaunch to get admin access will either not work or won't do any good. If that is what I need to do, I would think I can just add one line at the beginning like this and all would be good?:
Once I'm completely satisfied with this I thought I'd change the relaunch code from [ "runas", 1 ] to ["runas", 0 ] so the relaunched window will be hidden except for a brief flash. What do you think? Any other ways you can think of that this code could be strengthened, or made more flexible, quicker, useful, etc?
Even though I'm fairly confident in what I came up with, except not being sure about the handling for XP, I still had problems when I tried to use it to help in the original thread referenced at the very beginning of this post. Here is the code I came up with , setting up a scheduled task. (This is the first time I've ever tried to use the SCHTASKS command, or Powershell, or work with UAC, so I'm sure this is a major part of my problem. LOL) In this case setting it up to run "Everything.exe" ONLOGON and Start in system notification area only. (It of course assumes that Everything was installed to its "normal" location) (And I really need your help to verify correct behavior in XP.):
This code works for me when inserted in the "Your Batch HERE" section above in Win7 x64 Ultimate with UAC disabled, both in Win7 mode and if I force it to use the :treatAsXP section. But I could not get it to work on the Win7 x64 Home Premium with UAC enabled. I'd just get an access denied type of error. I also have no way to test if it would work correctly in XP.
I tried modifying the code like this using part of the latest suggestion from myselfidem:
It only sort of worked even on my Win7 x64 with UAC disabled. Note that I had to lose the "-startup" Everything option because I couldn't figure out the right way to escape the necessary double quotes. No matter what I tried, either it thought that '-startup' was being specified as an "Invalid argumentg/option" for SCHTASKS, or '(86)' was being specified as an invalid "cmdlet, function, script file, or operable program" for Powershell. On my wife's laptop I still got the access denied type error. When I tried to force the use of the :treatAsXP code on either machine, I got a prompt to "Please enter the run as password for _________:". No matter what I tried I got "ERROR: User credentials are not allowed on the local machine." This was true on my Win7 x64 Ultimate that does not have a password for my administrator level account and UAC is disabled, and on my wife's Win7 x64 Home Premium and her administrator level account and UAC is enabled.
Then I adapted some of the very original code from http://myunster.com/blog/server-administration/30.html that started this whole mess like this:
I have no idea what this did, if anything, in the Win7 code on my machine since I saw no results, errors, popups, messages, or feedback of any kind whatsoever, so I didn't even try it on my wife's machine. The XP code is the same as my original try so I know what it will do on both machines.
Just for reference, here is a copy of the code from http://myunster.com/blog/server-administration/30.html:
Obviously I'm doing something wrong, though I have no idea what else to try. Looking at the differences between this original code and what I have tried, part of the problem might be the '-startup' Everything argument. Also, he was scheduling things as system level tasks and I understood from the original Wincert thread that Everything needed to be scheduled as a user level task. Note from Geej:
I just don't know. There is also this info from mooms:
So to get this kind of thing working with Everything, at least using this kind of approach, we might have to wait on the Developer to add the appropriate hooks? It also indicates that the developer is willing to take input, so maybe we have some suggestions for him?
It also makes we want to know how you/Trouba got the ssApp/ppApp versions to behave for both Win7 and XP and can I use any similar type of trick in my code?
Sorry for the length of this post, but I would very much appreciate some feedback.
Cheers and Regards
* UAC is enabled or disabled
* Dealing with 32-bit and 64-bit
* Dealing with XP requirements vs Vista and onward, ie NT5 vs NT6, Handle at least XP, 2003, Vista, Win7, 2008, Win8, 2012 All versions Home to Ultimate to Server
* The possibility of dealing with batch parameters
* Handling file systems in different languages, ie dealing with special characters
* Ideally, code should work both during OS install and on a running system
* Depend on as few external files or programs as possible
* Be location independent and preferably be able to run correctly from CD/DVD, ie non-writeable source
* Be as small, fast, and unobtrusive as possible
Individually, most of the requirements are easy to deal with, but I've not seen anything that deals with them all at once. I personally have no immediate real need for such code, it started out just trying to help someone out improving a batch they already found. But you know me. If I'm going to write code, I will try to make it flexible, bulletproof, and all powerful! MWUAHAHAH! :cough:, :cough: Excuse me, I digress. Sorry. LOL It began with this post http://www.wincert.net/forum/topic/10458-everything-search-engine-v130632b/?p=90706.
[If there are better alternatives to doing this via batch I would like to know about them, but I would like to understand how to do this via batch if at all possible. I think I can understand the underlying issues better that way.]
I found many references to the Admin Access Prompt for Batch files issue. It seems the accepted process is to test to see if you already have Admin access, and if you don't, to relaunch the batch requesting Admin access and requiring the user to approve this. ( I would think it would be great if Admin rights could be obtained without requiring the user's approval, but I guess that would be a security issue. I also seem to never run into the need for this since I operate as an Admin with UAC disabled, but if I'm going to try and improve this batch, it would be best if it can operate correctly with UAC enabled. ) I'm also not totally sure how best to fit XP into this. Do you ignore all this if the OS is XP or does XP have to deal with the issue as well? It's been too long since I used XP and A just don't remember. I'll assume XP has to deal with it as well in this discussion.
So, to begin, assuming you have a batch that does a task, but it requires Admin access and you want to adapt it to work correctly with UAC enabled, what then? I found many examples of code that you can insert at the beginning of your batch code that is supposed to do the test and relaunch as described above.
https://sites.google.com/site/eneerge/scripts/batchgotadmin - BatchGotAdmin International-Fix Code:
Code: Select all
@echo off
:: BatchGotAdmin
:-------------------------------------
REM --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
exit /B
:gotAdmin
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
pushd "%CD%"
CD /D "%~dp0"
:--------------------------------------
<YOUR BATCH SCRIPT HERE>
NOTES:
* This is supposed to deal with the special character issue:
in the line
echo UAC.ShellExecute %0, "", "", "runas", 1 >> "%temp%\getadmin.vbs"
replace %0 with %~s0 because with a special character in the path (German Umlaut "ΓΌ"), the VBS interpreter didn't see it anymore. I guess that's a bug on M$'s part, so this is a workaround. (Info on that syntax: http://www.computerhope.com/forhlp.htm#03 %~sI expanded path contains short names only)
* It also does not depend on trying to write a dummy file to a restricted area as a test, nor does it use the old trick "AT > NUL" since AT depends on a service that is often disabled.
* But this method does not forward arguments
This is essentially what Rob van der Woude also lists [url]http://www.robvanderwoude.com/clevertricks.php[/url:
Code: Select all
:: Ensure ADMIN Privileges
:: adaptation of https://sites.google.com/site/eneerge/home/BatchGotAdmin and http://stackoverflow.com/q/4054937
@echo off
:: Check for ADMIN Privileges >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
if '%errorlevel%' NEQ '0' (
REM Get ADMIN Privileges
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
del "%temp%\getadmin.vbs"
exit /B
) else (
REM Got ADMIN Privileges
pushd "%cd%"
cd /d "%~dp0"
@echo on
)
I was having problems testing my code, because I couldn't reliably keep the relaunched cmd window open during code execution so I could use 'echo-pause' statements to see what was going on in the relaunched batch. Though similar to the above code, I didn't care for the exact method shown here http://jagaroth.livejournal.com/63875.html:
Code: Select all
@ECHO OFF
REM Changing working folder back to current directory
%~d0
CD %~dp0
REM Folder changed
REM Check first if Windows XP
for /f "tokens=3*" %%i IN ('reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName ^| Find "ProductName"') DO set vers=%%i %%j
echo %vers% | find "XP" > nul
if %ERRORLEVEL% == 0 goto ver_xp
REM Ask for admin access
if exist "admincheckOK.txt" goto adminOK1
del /Q admincheckOK.vbs
ECHO.
ECHO. Please wait...
echo.Set objShell = CreateObject("Shell.Application") > admincheckOK.vbs
echo.Set FSO = CreateObject("Scripting.FileSystemObject") >> admincheckOK.vbs
echo.strPath = FSO.GetParentFolderName (WScript.ScriptFullName) >> admincheckOK.vbs
echo.If FSO.FileExists(%0) Then >> admincheckOK.vbs
echo. Dim oShell >> admincheckOK.vbs
echo. Set oShell = WScript.CreateObject ("WScript.Shell") >> admincheckOK.vbs
echo. oShell.run "cmd.exe /c echo admincheckOK > admincheckOK.txt" >> admincheckOK.vbs
echo. Set oShell = Nothing >> admincheckOK.vbs
echo. objShell.ShellExecute "cmd.exe", " /c " ^& %0 ^& " ", "", "runas", 1 >> admincheckOK.vbs
echo.Else >> admincheckOK.vbs
echo. MsgBox "Script file not found" >> admincheckOK.vbs
echo.End If >> admincheckOK.vbs
cscript //B admincheckOK.vbs
goto timeend
:adminOK1
del /Q admincheckOK.txt
del /Q admincheckOK.vbs
:ver_xp
REM Admin Access allowed
<snip>... Your batch code here ...<snip>
REM Following statement required if Admin access denied
:timeend
del /Q admincheckOK.vbs
but it did give me the idea of spawning my relaunched batch in a specified "cmd.exe". This did wonders in stabilizing my test environment such that my relaunched code was always visible.
This next link - http://stackoverflow.com/questions/1894967/how-to-request-administrator-access-inside-a-batch-file:
Code: Select all
@echo off
:: BatchGotAdmin
:-------------------------------------
REM --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
set params = %*:"=""
echo UAC.ShellExecute "%~s0", "%params%", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
exit /B
:gotAdmin
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
pushd "%CD%"
CD /D "%~dp0"
:--------------------------------------
<YOUR BATCH SCRIPT HERE>
gave me an idea how to pass the batch parameters to the relaunched program, but again it just wasn't reliable for me. After playing around for a while, I came up with this:
Code: Select all
SET _tempvbs=%TEMP%.\GetAdmin.vbs
SET _params=%*
IF []==[%*] (GOTO:relaunch)
SET _params= %_params:"=""%
:relaunch :: Create a temporary VBScript and Relaunch this script 'As Admin'
SET _cmd= /C %~s0%_params%
ECHO>"%_tempvbs%" SET UAC = CreateObject^("Shell.Application"^)
ECHO>>"%_tempvbs%" UAC.ShellExecute "CMD.exe", "%_cmd%", "", "runas", 1
cscript "%_tempvbs%" //nologo
which was reliable. My very simplified testing on Win7 x64 with UAC disabled indicates that this reliably handles relaunching a batch program with or without parameters even if some or all of those parameters are enclosed in quotes. It even properly handles extra or empty quotes, such as Param-6 or Param-2 below:
Code: Select all
BatchProg Param-1, "", Param-3, "Param 4", Param-5, ""Param6"", Param-7
Improper parameters such as ""Param x"" will be handled the same way that they are usually handled, which is not the way you probably want them to be handled. The embedded space will break the single parameter into two parameters as far as the batch processor is concerned. I have code available that I could use to "correct" such a problem, but did not include it since I considered it beyond the scope of this exercise.
Note that I also picked up a tip along the way to specify cspript to launch the VBScript in case the user did not have .vbs files associated with a proper script engine.
An even better test might be "CALL NET SESSION >nul 2>&1" as shown here: http://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights/4054937#4054937 (NOTE: Even though "NET SESSION" is proven to be a valid test for Admin rights on XP, both of the next two coding examples skip the relaunch if XP is the detected OS. Why??):
Code: Select all
@echo off
REM Quick test for Windows generation: UAC aware or not ; all OS before NT4 ignored for simplicity
SET NewOSWith_UAC=YES
VER | FINDSTR /IL "5." > NUL
IF %ERRORLEVEL% == 0 SET NewOSWith_UAC=NO
VER | FINDSTR /IL "4." > NUL
IF %ERRORLEVEL% == 0 SET NewOSWith_UAC=NO
REM Test if Admin
CALL NET SESSION >nul 2>&1
IF NOT %ERRORLEVEL% == 0 (
if /i "%NewOSWith_UAC%"=="YES" (
rem Start batch again with UAC
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
del "%temp%\getadmin.vbs"
exit /B
)
echo.
echo Program works only with admin rights. Please start again with admin rights!
pause
goto :eof
)
along with experimental proof by Ben Hooper that this test for admin rights works on XP - Win8:
Then there was a discussion that began here - http://ss64.org/viewtopic.php?id=1491 suggesting that the batch command "OPENFILES" is the best test for admin rights and is the least likely to get changed by MS as the OS evolves (It wasn't "proven", but I assume that "OPENFILES" is just as valid a test as "CALL NET SESSION >nul 2>&1" was proven above.):
Code: Select all
VER | FIND "Version 6." > nul
IF %ERRORLEVEL% == 0 (
REM Do OPENFILES to check for administrative privileges
REM 2>nul is needed because 2012 Server returns ERRORLEVEL 0 but outputs "ERROR: Unable to retrieve data."
OPENFILES >nul 2>nul
IF ERRORLEVEL 1 (
COLOR CF
ECHO.ERROR: Logged-on user does not have administrative privilege.
ECHO.Right click on this bat file and select 'Run as administrator'.
PAUSE
EXIT /B 1
)
)
and that cumulated with the example given here http://ss64.com/vb/syntax-elevate.html:
Code: Select all
@Echo off
Set _tempfile=%LocalAppData%\SaveDrives.txt
Set _tempvbs=%LocalAppData%\getadmin.vbs
:: Check for admin permissions
OPENFILES > nul
:: If error flag set, we do not have admin.
If ERRORLEVEL 1 (
Echo Requesting administrative privileges...
Goto sub_elevate
) Else ( Goto sub_main )
:sub_elevate
:: Save the current drive mappings to a temp file
Del %_tempfile% 2>Nul
For /f "tokens=2" %%G in ('net use ^| find ":"') do (call :sub_save_drive %%G)
:: Create a temporary VBScript
Echo Set objShell = CreateObject^("Shell.Application"^) > %_tempvbs%
Echo objShell.ShellExecute "%~f0", "", "", "runas", 1 >> %_tempvbs%
:: Relaunch this script 'As Admin'
cscript "%_tempvbs%" //nologo
Exit /B
:sub_save_drive
Set _drive=%1
For /f "tokens=2,*" %%I in ('Net Use %_drive% ^|Find "\\"') Do (Set _Path=%%J)
Echo %_drive%~%_Path%>>%_tempfile%
Goto :eof
:sub_main
:--------------------------------------
:: At this point we should be running under an Admin token
If exist "%_tempvbs%" ( Del "%_tempvbs%" )
:: Re-Map the drives listed in the temp file (if any)
For /f "tokens=1,2 delims=~" %%G in (%_tempfile%) do (Net Use %%G /delete /y & Net Use %%G "%%H" /persistent:no)
If exist "%_tempfile%" ( Del "%_tempfile%" )
Echo == List the current drive mappings ==
Net Use
:: ** Add extra commands you want to run elevated here **
pause
NOTE: They brought up the point that when a script is run with elevated permissions, several aspects of the user environment may change: The current directory, the current TEMP folder and any mapped drives will be disconnected. So the script saves the current drive mappings and re-maps them once the session has been elevated.
I then also considered the possibility of the original batch being called by something that did not expect to have control returned until the batch was finished. Even launching the elevated batch via -- START "Relaunch" /WAIT cscript "%_tempvbs%" //nologo -- doesn't really do any good since the VBScript launches the elevated batch and then closes, releasing the calling batch from the "/WAIT" condition before the elevated batch is finished. So since I had to create the temporary batch file anyway, I figured I would use that as my flag file and not delete that via the elevated batch until it was done and I added a wait loop in the calling batch to wait for it. That also means that if the elevated batch hangs or does not complete normally, that the file will not be deleted and the calling batch will wait forever until canceled by the user. I wish I had a better idea, but that was the best I could come up with.
So taking all I learned above, I combined all of that into the following:
Code: Select all
@echo off & SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
:-------------------------------------
:: This script section will check if it is running with elevated (Admin) permissions and if not,
:: the script will popup a UAC prompt and relaunch itself. When a script is run with elevated
:: permissions several aspects of the user environment may change: The current directory, the
:: current TEMP folder and any mapped drives will be disconnected. So the script saves the
:: current drive mappings and re-maps them once the session has been elevated. Any parameters
:: that the script had when originally envoked are preserved and passed to the elevated batch.
:: The originally envoked script will wait for the elevated script to finish before it closes.
:-------------------------------------
CALL :sub_CleanUp
SET _tempfile=%TEMP%.\SaveDrives.txt
SET _tempvbs=%TEMP%.\GetAdmin.vbs
REM --> Check for admin permissions, If the error flag does NOT get set, we already have admin rights.
(OPENFILES >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:gotAdmin)
REM --> Save the current drive mappings to a temp file
DEL %_tempfile% >NUL 2>&1
FOR /F "tokens=2" %%G IN ('NET USE ^| FIND ":"') DO (CALL :sub_SaveDrive %%G)
REM --> Preserve any script parameters
SET _params=%*
IF []==[%*] GOTO:relaunch
SET _params= %_params:"=""%
:relaunch :: Create a temporary VBScript that will Relaunch this script 'As Admin'
SET _cmdPlusParams= /C %~s0%_params%
ECHO>"%_tempvbs%" SET UAC = CreateObject("Shell.Application")
ECHO>>"%_tempvbs%" UAC.ShellExecute "CMD.exe", "%cmdPlusParams%", "", "runas", 1
START "Relaunch" /WAIT cscript "%_tempvbs%" //nologo
:waitForAsAdminToFinish
IF EXIST "%_tempvbs%" (GOTO:waitForAsAdminToFinish)
GOTO:exit
:sub_SaveDrive
SET _drive=%1
FOR /F "tokens=2,*" %%I IN ('NET USE %_drive% ^|FIND "\\"') DO (SET _path=%%J)
ECHO>>%_tempfile% %_drive%~%_path%
EXIT /B
:gotAdmin :: At this point we should be running under an Admin token
:--------------------------------------
REM --> Re-Map the drives listed in the temp file (if any), then delete temp file
IF EXIST "%_tempfile%" (
FOR /F "tokens=1,2 delims=~" %%G IN (%_tempfile%) DO (NET USE %%G /delete /y & NET USE %%G "%%H" /persistent:no)
DEL %_tempfile% >NUL 2>&1
)
PUSHD "%~dp0"
:--------------------------------------
:--------------------------------------
::< *** ADD YOUR BATCH SCRIPT THAT YOU WANT TO RUN ELEVATED HERE *** >
:--------------------------------------
:--------------------------------------
POPD
GOTO:exit
:sub_CleanUp
FOR /F "tokens=1* delims==" %%G IN ('"SET "_" 2>nul"') DO (SET "%%G=")
EXIT /B 0
:exit :: Delete temp VBScript to let original script know we're finished and reset any local variables used
IF EXIST "%_tempvbs%" ( DEL "%_tempvbs%" >NUL 2>&1 )
CALL :sub_CleanUp
ENDLOCAL & EXIT
This code seems to work fine on my Win7 x64 Ultimate with UAC disabled, but then in that situation not much is being done, is it? So I forced it to go though the relaunch by REM'ing out the line -- (OPENFILES >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:gotAdmin) -- and the batch relaunched with all parameters being passed along appropriately. I then tried it on my wife's laptop which has Win7 x64 Home Premium with UAC enabled. With an "ECHO--PAUSE" statement in the "Your Batch HERE" section it tested fine. The batch was relaunched, the UAC prompt was displayed and once approved the "echo" statement displayed. Once I then pressed "Any Key" the window closed and everything closed and cleaned up correctly. So far so good.
But I need to know if it's appropriate to treat XP and 2003, ie NT5, the same way or should I test for NT5 at the beginning and skip straight to :gotAdmin if NT5 is detected? I guess I should skip the relaunch if trying to relaunch to get admin access will either not work or won't do any good. If that is what I need to do, I would think I can just add one line at the beginning like this and all would be good?:
Code: Select all
CALL :sub_CleanUp
SET _tempfile=%TEMP%.\SaveDrives.txt
SET _tempvbs=%TEMP%.\GetAdmin.vbs
REM --> Don't bother trying to elevate batch for XP
(VER | FIND /I "Version 5." >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:gotAdmin)
REM --> Check for admin permissions, If the error flag does NOT get set, we already have admin rights.
....
Once I'm completely satisfied with this I thought I'd change the relaunch code from [ "runas", 1 ] to ["runas", 0 ] so the relaunched window will be hidden except for a brief flash. What do you think? Any other ways you can think of that this code could be strengthened, or made more flexible, quicker, useful, etc?
Even though I'm fairly confident in what I came up with, except not being sure about the handling for XP, I still had problems when I tried to use it to help in the original thread referenced at the very beginning of this post. Here is the code I came up with , setting up a scheduled task. (This is the first time I've ever tried to use the SCHTASKS command, or Powershell, or work with UAC, so I'm sure this is a major part of my problem. LOL) In this case setting it up to run "Everything.exe" ONLOGON and Start in system notification area only. (It of course assumes that Everything was installed to its "normal" location) (And I really need your help to verify correct behavior in XP.):
Code: Select all
:SchTasksEverything
REM --> Set SchTasks specific variables - %ProgramFiles% will be used if OS is 32-bit, %ProgramFiles(x86)% if OS is 64-bit
(SET "_Schedule=ONLOGON")
(SET "_TaskName=Everything")
(SET "_X86=(x86)") & (IF /I "%PROCESSOR_ARCHITECTURE%"=="x86" (IF NOT DEFINED PROCESSOR_ARCHITEW6432 (SET "_X86=")))
(SET _Task=\"%%ProgramFiles%_X86%%%\Everything\Everything.exe\" -startup)
(VER | FIND /I "Version 5." >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:treatAsXP)
SCHTASKS /Create /TN "%_TaskName%" /TR "%_Task%" /SC %_Schedule% /RL HIGHEST /F
(GOTO:done)
:treatAsXP
REM --> Using FINDSTR is necessary since WinXP does not support TN for schtasks /query, also /F is not an option for schtasks /create
(SET _Result="-1") & FOR /F "delims=, tokens=2" %%R IN ('SCHTASKS /query /fo csv /v ^| FINDSTR /IL /C:"%_TaskName%"') DO (SET _Result=%%R)
(SET _Result=%_Result:\=%) & IF "%_TaskName%"==!_Result! (SCHTASKS /Delete /TN "%_TaskName%" /F)
SCHTASKS /Create /TN "%_TaskName%" /TR "%_Task%" /SC %_Schedule%
:done
This code works for me when inserted in the "Your Batch HERE" section above in Win7 x64 Ultimate with UAC disabled, both in Win7 mode and if I force it to use the :treatAsXP section. But I could not get it to work on the Win7 x64 Home Premium with UAC enabled. I'd just get an access denied type of error. I also have no way to test if it would work correctly in XP.
I tried modifying the code like this using part of the latest suggestion from myselfidem:
Code: Select all
:SchTasksEverything
REM --> Set SchTasks specific variables - %ProgramFiles% will be used if OS is 32-bit, %ProgramFiles(x86)% if OS is 64-bit
(SET "_Schedule=ONLOGON")
(SET "_TaskName=Everything")
(SET "_X86=(x86)") & (IF /I "%PROCESSOR_ARCHITECTURE%"=="x86" (IF NOT DEFINED PROCESSOR_ARCHITEW6432 (SET "_X86=")))
(SET _Task=\"%%ProgramFiles%_X86%%%\Everything\Everything.exe\")
(VER | FIND /I "Version 5." >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:treatAsXP)
REG ADD "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" /t REG_SZ /d RUNASADMIN /f >NUL
SET _Argument=SCHTASKS /Create /TN "%_TaskName%" /TR "%_Task%" /SC ONLOGON /RL HIGHEST /F
SET _ELEVATED_CMD=PowerShell -Command "%_Argument%"
IF EXIST "%~0.ELEVATED" DEL /F "%~0.ELEVATED" >NUL 2>&1
ECHO>"%~0.ELEVATED" %_ELEVATED_CMD%
CALL %_ELEVATED_CMD%
DEL /F "%~0.ELEVATED" >NUL 2>&1
(GOTO:done)
:treatAsXP
REM --> Using FINDSTR is necessary since WinXP does not support TN for schtasks /query, also /F is not an option for schtasks /create
(SET _Result="-1") & FOR /F "delims=, tokens=2" %%R IN ('SCHTASKS /query /fo csv /v ^| FINDSTR /IL /C:"%_TaskName%"') DO (SET _Result=%%R)
(SET _Result=%_Result:\=%) & IF "%_TaskName%"==!_Result! (SCHTASKS /Delete /TN "%_TaskName%" /F)
SCHTASKS /Create /TN "%_TaskName%" /TR "%_Task%" /SC ONLOGON /S %userdomain% /U %username% /RU %username%
:done
It only sort of worked even on my Win7 x64 with UAC disabled. Note that I had to lose the "-startup" Everything option because I couldn't figure out the right way to escape the necessary double quotes. No matter what I tried, either it thought that '-startup' was being specified as an "Invalid argumentg/option" for SCHTASKS, or '(86)' was being specified as an invalid "cmdlet, function, script file, or operable program" for Powershell. On my wife's laptop I still got the access denied type error. When I tried to force the use of the :treatAsXP code on either machine, I got a prompt to "Please enter the run as password for _________:". No matter what I tried I got "ERROR: User credentials are not allowed on the local machine." This was true on my Win7 x64 Ultimate that does not have a password for my administrator level account and UAC is disabled, and on my wife's Win7 x64 Home Premium and her administrator level account and UAC is enabled.
Then I adapted some of the very original code from http://myunster.com/blog/server-administration/30.html that started this whole mess like this:
Code: Select all
:SchTasksEverything
REM --> Set SchTasks specific variables - %ProgramFiles% will be used if OS is 32-bit, %ProgramFiles(x86)% if OS is 64-bit
(SET "_Schedule=ONLOGON")
(SET "_TaskName=Everything")
(SET "_X86=(x86)") & (IF /I "%PROCESSOR_ARCHITECTURE%"=="x86" (IF NOT DEFINED PROCESSOR_ARCHITEW6432 (SET "_X86=")))
(SET _Task=\"%%ProgramFiles%_X86%%%\Everything\Everything.exe\" -startup)
(VER | FIND /I "Version 5." >NUL 2>&1) & IF '0'=='!ERRORLEVEL!' (GOTO:treatAsXP)
REG ADD "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers" /v "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" /t REG_SZ /d RUNASADMIN /f >NUL
SET _Argument=SCHTASKS /Create /TN \"%_TaskName%\" /TR \"%_Task%\" /SC %_Schedule% /RL HIGHEST /F
SET _ELEVATED_CMD=PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('Cmd.exe', '/C %_Argument%', '', 'runas', 1)
IF EXIST "%~0.ELEVATED" DEL /F "%~0.ELEVATED" >NUL 2>&1
ECHO>"%~0.ELEVATED" %ELEVATED_CMD%
CALL %ELEVATED_CMD%
DEL /F "%~0.ELEVATED" >NUL 2>&1
(GOTO:done)
:treatAsXP
REM --> Using FINDSTR is necessary since WinXP does not support TN for schtasks /query, also /F is not an option for schtasks /create
(SET _Result="-1") & FOR /F "delims=, tokens=2" %%R IN ('SCHTASKS /query /fo csv /v ^| FINDSTR /IL /C:"%_TaskName%"') DO (SET _Result=%%R)
(SET _Result=%_Result:\=%) & IF "%_TaskName%"==!_Result! (SCHTASKS /Delete /TN "%_TaskName%" /F)
SCHTASKS /Create /TN "%_TaskName%" /TR "%_Task%" /SC %_Schedule%
:done
I have no idea what this did, if anything, in the Win7 code on my machine since I saw no results, errors, popups, messages, or feedback of any kind whatsoever, so I didn't even try it on my wife's machine. The XP code is the same as my original try so I know what it will do on both machines.
Just for reference, here is a copy of the code from http://myunster.com/blog/server-administration/30.html:
In my current job we needed to automatize some processes by using Windows Task Scheduler; I wrote a batch file, which you may find useful,
especially when you don't have direct/remote access to user's desktop.
The main problem of implementation this task was Windows Vista/7 with its UAC, I had to use powershell to get Admin privileges by popping a window prompt to user directly to accept rather than have the user type the password manually upon the request.
Code: Select all
::*******************************************::
:: schedule.bat ::
:: Set variables TaskName, Task and go ahed ::
:: http://myunster.com ::
::*******************************************::
@ECHO off
ECHO "Proceeding..."
REM Delete variables, may be cached
SET "TaskName="
SET "Task="
REM Set variables
SET TaskName=DummyTaskName
REM Following task will be executed every hour
SET Task=C:\www.domain.dev\usr\local\php5\php.exe C:\www.domain.dev\cron.php
REM Determine if windows xp
VER | find "XP" > NUL
IF %ERRORLEVEL% == 0 GOTO ver_xp
REM Determine if windows Vista/Win7
systeminfo | find "OS Name" > %TEMP%\osname.txt
FOR /F "usebackq delims=: tokens=2" %%i IN (%TEMP%\osname.txt) DO SET Version=%%i
DEL /F %TEMP%\osname.txt
ECHO %Version% | find "Windows 7" > NUL
IF %ERRORLEVEL% == 0 GOTO ver_7
ECHO %Version% | find "Windows Vista" > NUL
IF %ERRORLEVEL% == 0 GOTO ver_vista
:ver_xp
:Run Windows XP specific commands here.
REM Delete variable, may be cached
SET "Result="
REM WinXP doesn't support TN for schtasks /query
FOR /F "delims=, tokens=2" %%R IN ('schtasks /query /fo csv /v ^| findstr /L /C:"%TaskName%"') DO SET Result=%%R
IF (%Result%)==() SET Result="-1"
IF "%TaskName%" == %Result% (
REM Delete Task if it exists
SCHTASKS /Delete /TN "%TaskName%" /F
)
REM Then Create hourly running one
SCHTASKS /Create /TN "%TaskName%" /TR "%Task%" /SC HOURLY /RU SYSTEM
GOTO exit
:ver_vista
:Run Windows Vista specific commands here.
GOTO Elevation
:ver_7
:Run Windows 7 specific commands here.
GOTO Elevation
:Elevation
REM Don't forget escape double quotes for CMD argument that you will pass to powershell
PushD "%~dp0"
IF EXIST "%~0.ELEVATED" DEL /F "%~0.ELEVATED"
SET Argument=SCHTASKS /Create /F /TN \"%TaskName%\" /TR \"%Task%\" /SC HOURLY /RU SYSTEM
SET ELEVATED_CMD=PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('Cmd.exe', '/C %Argument%', '', 'runas')
ECHO %ELEVATED_CMD% >> "%~0.ELEVATED"
CALL %ELEVATED_CMD%
DEL /F "%~0.ELEVATED"
PopD
GOTO exit
:exit
ECHO "Done!"
EXIT
Obviously I'm doing something wrong, though I have no idea what else to try. Looking at the differences between this original code and what I have tried, part of the problem might be the '-startup' Everything argument. Also, he was scheduling things as system level tasks and I understood from the original Wincert thread that Everything needed to be scheduled as a user level task. Note from Geej:
Just my thought on the followings (may not be 100% right, still learning...)
The reason why the original code for XP did not work is because {/RU "SYSTEM"} runs as {NT AUTHORITY\SYSTEM} context.
This context is non-interactive. That is why no system tray icon shown up on your systray.
(non-interactive means you can't bring up the program GUI to make your search as you cannot interact with the program interface).
So to create an interactive logon, you need to provide a user name & password. This password is use to login to XP.
My XP do not have login password as it is autoboot+login to desktop w/o password.
Hence I have to create a password (I use justforfun as password as an example) for myself so that I can create a task schedule.
I just don't know. There is also this info from mooms:
I have asked David Carpenter, developer of Everything, if he can rewrite his installer, he have answered that the next major release will be an client-server approach, eliminating the problems with UAC and the fact that anything you run from "Everything" is being elevated.
It might also be open-sourced.
So to get this kind of thing working with Everything, at least using this kind of approach, we might have to wait on the Developer to add the appropriate hooks? It also indicates that the developer is willing to take input, so maybe we have some suggestions for him?
It also makes we want to know how you/Trouba got the ssApp/ppApp versions to behave for both Win7 and XP and can I use any similar type of trick in my code?
Sorry for the length of this post, but I would very much appreciate some feedback.
Cheers and Regards