Batch script to run as both regular user and admin

Discussion in 'Scripting' started by sacarias, Jul 13, 2019.

  1. sacarias

    sacarias MDL Novice

    Nov 21, 2018
    33
    1
    0
    I need to make a batch script to some some tasks: write and modify some few configurations in %userprofile% (unprivileged user), and query some registry info which needs administrator rights.

    The thing is, if I right click the script and run it as administrator, all stuff will be run as the *administrator* user (whoever it is) instead of my currently logged in %username%, and thus all changes in %userprofile% will be for the administrator user instead of the current one.

    Also, I can't just "run as <particular_username>" because I need to use the script in different PCs, so I do need to use a variable.

    First easy obvious option would be just splitting it into 2 different scripts, one for user and one to be run as admin. But I'd really like to make it in one single script if possible.

    I have tried to imagine possible "solutions": run the script as admin and somehow get current logged in %username%, or run as regular user and somehow automatically ask for admin privileges once reaching the part which needs it.
    But I don't know how to do any of them.

    Could someone help please?
    Thanks very much in advance.

    @abbodi1406:
    Hope you can see this if you have chance (and time...)
     
  2. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    9,480
    35,389
    300
  3. BAU

    BAU MDL Senior Member

    Feb 10, 2009
    366
    578
    10
    You could self-elevate the script on your own terms, passing the arguments you need like in this example:
    Code:
    @echo off
    
    :: grab current user's SID [string-safe in scripts unlike using the name]
    for /f "tokens=2" %%s in ('whoami /user /fo list') do set CU=%%s
    
    :: set arguments to pass userprofile and currentuser above and any other command line parameters to self-elevate via powershell
    set "args=set "__UP__=%USERPROFILE%" &set "__CU__=%CU%" &set ?=y&call "%~f0" %*" &call set "args=%%args:"=\""%%"
    
    ::AveYo: self-elevate passing args and preventing loop
    reg query HKU\S-1-5-20 >nul 2>nul || if "%?%" neq "y" (powershell -c "start cmd -ArgumentList '/c %args%' -verb runas" &exit)
    :: should be elevated here
    
    echo [CURRENT USER] %__UP__%
    wmic useraccount where sid='%__CU__%' get name,sid /format:table
    
    if not defined __CU__ echo .. %%__CU__%% is not available because you've right-click Run as administrator &echo. &echo.
    
    echo [RUN AS ADMIN] %USERPROFILE%
    wmic useraccount where sid='%CU%' get name,sid /format:table
    
    pause
    exit/b
    Or group actions based on required rights, and only elevate when necessary like in this example:
    Code:
    @echo off
    
    if defined __JUMP__ goto :%__JUMP__%
    
    :peasant
    echo doin' stuff not requiring admin rights
    whoami /user /fo list
    echo.
    rem pause
    
    echo asking for admin rights...
    
    :: set arguments to jump to :admin label and pass any other command line parameters to self-elevate via powershell
    set "args=set __JUMP__=admin &set ?=y&call "%~f0" %*" &call set "args=%%args:"=\""%%"
    ::AveYo: self-elevate passing args and preventing loop
    reg query HKU\S-1-5-20 >nul 2>nul || if "%?%" neq "y" (powershell -c "start cmd -ArgumentList '/c %args%' -verb runas" &exit)
    :: should be elevated here
    
    goto :admin
    exit/b
    
    :admin
    echo doin' stuff requiring admin rights
    whoami /user /fo list
    echo.
    pause
    exit/b
    
     
  4. TigTex

    TigTex MDL Member

    Oct 5, 2009
    170
    143
    10
    I do this on my scripts. Because it doesn't use powershell, works with windows vista and later operating systems
    Code:
    
    INSERT HERE THE UNPRIVILEGED USER CODE
    
    (
        >nul fsutil dirty query %SYSTEMDRIVE% 2>&1 || (
            :: Create VBS script
            echo Set UAC = CreateObject^("Shell.Application"^)>"%TEMP%\elevate.vbs"
            echo UAC.ShellExecute "%~f0", "%TEMP%\elevate.vbs", "", "runas", 1 >>"%TEMP%\elevate.vbs"
            if exist "%TEMP%\elevate.vbs" start /b /wait >nul cscript /nologo "%TEMP%\elevate.vbs" 2>&1
            :: Delete elevation script if exist
            if exist "%TEMP%\elevate.vbs" >nul del /f "%TEMP%\elevate.vbs" 2>&1
            exit /b
        )
    )
    
    INSERT HERE THE CODE THAT NEEDS ADMIN PERMISSIONS
    
    
    Maybe it helps you
     
  5. sacarias

    sacarias MDL Novice

    Nov 21, 2018
    33
    1
    0
    Thanks very much everyone for your advise.

    Certainly, what I'm actually seeking is grouping actions based on required rights; but also that actions done by regular user remain regular user's (in terms of file ownerships, permissions, stamps, etc), and admin actions remain admin's.

    So with that in mind, I have this:
    Code:
    @echo off
    
    %windir%\system32\reg.exe query "HKU\S-1-5-19" > nul 2>&1 && (
    REM assuming user right-clicked -> run as admin
    echo ERROR: you must not run as admin
    echo.
    echo Press any key to exit...
    pause > nul
    goto :eof
    )
    
    REM doing stuff as non-privileged user
    [...]
    
    (
            %windir%\system32\reg.exe query "HKU\S-1-5-19" > nul 2>&1 || (
    
            REM Create VBS script
            echo Set UAC = CreateObject^("Shell.Application"^) > "%TEMP%\elevate.vbs"
            echo UAC.ShellExecute "%~f0", "%TEMP%\elevate.vbs", "", "runas", 1 >> "%TEMP%\elevate.vbs"
            if exist "%TEMP%\elevate.vbs" start /b /wait > nul cscript /nologo "%TEMP%\elevate.vbs" 2>&1
    
            REM Delete elevation script if exist
            if exist "%TEMP%\elevate.vbs" > nul del /f "%TEMP%\elevate.vbs" 2>&1
            exit /b
        )
    )
    
    REM doing stuff as privileged user
    [...]
    
    echo Process complete
    echo Press any key to finish...
    pause > nul
    Maybe I'm wrong with the idea, advise always welcome...

    I tested both options from both @BAU (his second one) and @TigTex.
    Both do the job indeed, albeit I noticed they work by opening a brand new CMD session for the admin user alone once permission has been granted. For what I'm seeking this has a downpoint: this new session runs whole script from beginning again, and since script tells not to run as admin, the job is naturally never completed.

    Opening a new CMD session window necessarily, is this the only way it works? Or could there be a workaround?

    Another option I thought about was just using labels for each case, regular or admin, and goto corresponding label according to case. This would imply running script twice indeed, though at least better than having 2 scripts...
     
  6. rpo

    rpo MDL Addicted

    Jan 3, 2010
    905
    666
    30
    You could pass a parameter. Change
    Code:
    echo UAC.ShellExecute "%~f0", "%TEMP%\elevate.vbs", "", "runas", 1 >> "%TEMP%\elevate.vbs"
    by
    Code:
    echo UAC.ShellExecute "%~f0", "%Params%", "", "runas", 1 >> "%TEMP%\elevate.vbs"
    where %Params% represents parameters you pass to the script. Test for the existence of the parameter at the begining of the script.
     
  7. sacarias

    sacarias MDL Novice

    Nov 21, 2018
    33
    1
    0
    #7 sacarias, Jul 16, 2019
    Last edited: Jul 16, 2019
    (OP)
    Sorry, er... I'm actually a novice in all this matter and what I have done until now was based on limited knowledge and a lot of internet searches.
    Could you perhaps elaborate a bit more please?

    Like putting all stuff meant to be run as admin somewhat inside a %params% variable, or something like that?

    Thanks.

    PS: by the way, would all what I'm seeking still be "orthodox" (stable for work most of times), or am I actually going too "hacky" already and I better run the script twice?
     
  8. BAU

    BAU MDL Senior Member

    Feb 10, 2009
    366
    578
    10
    #8 BAU, Jul 17, 2019
    Last edited: Jul 31, 2019
    It does not happen on my example - script runs first part as user, and just second part as admin - that's what the jump stuff is there for.
    It's the core directive behind elevation - you must spawn another process somehow! No workaround - by design.

    Anyway, it seems there is a lack of proper underlying user detection that I'm going to solve right here:
    Code:
    @echo off
    
    REM IT SHOULD NOT MATTER IF THE USER HAS RIGHT-CLICKED RUN AS ADMIN THE SCRIPT OR NOT
    
    ::AveYo: self-elevate passing args and preventing loop
    set "args="%~f0" %*" & call set "args=%%args:"=\""%%"
    reg query HKU\S-1-5-19>nul||(if "%?%" neq "y" powershell -c "start cmd -ArgumentList '/c set ?=y&call %args%' -verb runas" &exit)
    
    REM NOW DEFINITELY RUNNING AS ADMIN - EITHER WAY
    
    ::AveYo: get Domain\User irrespective of script being run as Admin - another of my neat tricks :)
    for /f tokens^=^13^ delims^=^" %%u in ('tasklist /fi "imagename eq explorer.exe" /fo csv /v /nh') do if "%%u" neq "," set "DU=%%u"
    echo Domain\User: %DU%
    
    ::AveYo: simple replacement to get just the User
    call set "CU=%%DU:%COMPUTERNAME%\=%%"
    echo User: %CU%
    
    ::AveYo: get SID for User - can be input in my reg_takeownership snippet or icacls instead of domain\name
    for /f "tokens=1" %%s in ('wmic useraccount where ^(name^="%CU%" and domain^="%COMPUTERNAME%"^) get sid /value') do set "%%s">nul
    echo SID: %SID%
    
    ::AveYo: and the Domain\User running as admin being:
    echo Domain\User (Elevated): %COMPUTERNAME%\%USERNAME%
    
    echo.
    
    ::AveYo: now let's put it to a test with a real-life example
    takeown /f "C:\Program Files\Windows NT\Accessories\*" /r /skipsl /a
    icacls "C:\Program Files\Windows NT\Accessories\*" /grant:r "%DU%":F
    
    pause
    exit
    
    
    So with this method at your disposal, it no longer matters at which point you decide to elevate the script yourself, as you can determine the underlying user if the script was right-clicked - run as admin.
    The most convenient is at the beginning, but you can also group actions by rights (properly this time) as in my 2nd example, and then ask for elevation just for the admin part (and can make that window hidden - just google powershell start-process)
     
  9. sacarias

    sacarias MDL Novice

    Nov 21, 2018
    33
    1
    0
    @BAU:
    Thanks yet again for all your advise.
    I'll double-check the options again.

    Finally, and just curious, do you think it could be possible to do without powershell, i.e., only with cmd stuff?

    Thanks again.
     
  10. BAU

    BAU MDL Senior Member

    Feb 10, 2009
    366
    578
    10
    there are not that many other ways to elevate (mshta/rundll32/jscript/vbscript/schtasks/wmic) and most have uglier syntax than the powershell one-liner, and the show-stopper is that anti-virus software including built-in defender flag those other alternatives as malicious.
    use powershell - it's more convenient and more compatible (comes with windows since 7) and all chances are it wont be blocked by some lame av or sys admin even before reaching your users
     
  11. rpo

    rpo MDL Addicted

    Jan 3, 2010
    905
    666
    30
    I believe powershell is the way to go. With vbscript you may encounter issues when directory names include special or foreign characters.
    @BAU : great scripting!
     
  12. presto1234

    presto1234 MDL Novice

    Nov 16, 2015
    21
    19
    0
    #12 presto1234, Jul 19, 2019
    Last edited: Jul 19, 2019
    Hi, I think you can use this script https://forums.mydigitallife.net/th...ts-and-special-chars-incl-quote-no-tmp.74332/
    If you embed the VBS part using the "elevate_no-arguments" code and embed it as described, you can run the script without ever making any temporary files (write is a bottleneck if you think about it) and the thing looks Waaaaaay cleaner.
    If you use the normal VBS elevate code, you can even use some special characters that normally cause VBS to stop working, like quotes and others.

    Ofcourse Powershell is more functional, but unless a very sensitive AV or system administrator blocks VBS, CMD or embedded scripts, in 80% of the other cases this should run nearly everywhere, should work even on XP.

    You could use the script like this:
    Code:
    <!-- : Begin batch script
    @ECHO OFF
    
    FSUTIL dirty query "%SYSTEMDRIVE%" >NUL && (
      call :AdminPart
    ) || (
       ECHO(*************************************
       ECHO(Invoking UAC for Privilege Escalation
       ECHO(*************************************
       CSCRIPT //nologo "%~f0?.wsf" //job:ELAV /File:"%~f0"
       call :UserPart
    )
    
    echo(=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    echo( This code is run as either Admin or User
    echo( I'll just add a line to make things look neat
    echo(
    echo(Below are all the environment variables
    echo(
    set
    echo(=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    pause
    exit /b
    
    :AdminPart
    echo( Only runs as Admin and stuff
    echo( I run as %USERNAME% and my userprofile is %userprofile%
    exit /b
    
    :UserPart
    echo( Only runs as current user and stuff
    echo( I run as %USERNAME% and my userprofile is %userprofile%
    exit /b
    
    ----- Begin wsf script --->
    <package>
       <job id="ELAV">
          <script language="VBScript">
             Set strArg = WScript.Arguments.Named
             If Not strArg.Exists("File") Then
                Wscript.Echo "Switch /File:<File> is missing."
                WScript.Quit 1
             End If
             CreateObject("Shell.Application").ShellExecute strArg("File"), ELAV, "", "runas", 1
          </script>
       </job>
    </package>