4

1. Problem

I have a complicated batch file where some parts need to run with elevated/admin rights (e.g. interacting with Windows services) and I found a Powershell way to do that:

powershell.exe -command "try {$proc = start-process -wait -Verb runas -filepath '%~nx0' -ArgumentList '<arguments>'; exit $proc.ExitCode} catch {write-host $Error; exit -10}"

But there's a huge caveat! The elevated instance of my script (%~nx0) starts with a fresh copy of environment variables and everything I set "var=content" before is unavailable.

2. What I've tried so far

This Powershell script doesn't help either because Verb = "RunAs" requires UseShellExecute = $true which in turn is mutually exclusive to/with StartInfo.EnvironmentVariables.Add()

$p = New-Object System.Diagnostics.Process
$p.StartInfo.FileName = "cmd.exe";
$p.StartInfo.Arguments = '/k set blasfg'
$p.StartInfo.UseShellExecute = $true;
$p.StartInfo.Verb = "RunAs";
$p.StartInfo.EnvironmentVariables.Add("blasfg", "C:\\Temp")

$p.Start() | Out-Null
$p.WaitForExit()
exit $p.ExitCode

And even if that would work I'd still need to transfer dozens of variables...

3. unappealing semi-solutions

because circumventing the problem is no proper solution.

  1. helper tools like hstart - because I can't relay on external tools. Only CMD, Powershell and maybe VBscript (but it looks like runas plus wait and errorlevel/ExitCode processing isn't possible with/in vbs).
  2. passing (only required) variables as arguments - because I need dozens and escaping them is an ugly chore (both the result and doing it).
  3. restarting the whole script - because it's inefficient with all the parsing, checking processing and other tasks happening again (and again and ...). I'd like to keep the elevated parts to a minimum and some actions can later be run as a normal user (e.g service start/stop).
  4. Writing the environment to a file and rereading it in the elevated instance - because it's an ugly hack and I'd hope there's a cleaner option out there. And writing possibly sensitive information to a file is even worse than storing it temporarily in an environment variable.
Limer
  • 164
  • 1
  • 10
  • 1
    The environment variables not being passed is by design (and a restriction of `ShellExecute`, not PowerShell); it protects elevated applications from getting passed corrupted environments by malicious programs (for example, with a tweaked `PATH` that could trick the elevated app into launching something untoward). If I can offer another "unappealing semi-solution": pass only the required variables as arguments, but make the escape mechanism uniform (for example, a Base64-encoded compressed JSON object, reverse steps on receiving). Still quite a bit of code, but only has to be clever once. – Jeroen Mostert Mar 07 '22 at 18:10
  • As an aside: if the batch file is invoking _itself_ with elevation, you should use `%~f0` – mklement0 Mar 07 '22 at 18:11
  • @JeroenMostert - I know. It just gets a new environment copy from the shell which is most likely `explorer.exe` and I'm aware of the reason. The Base64 idea is better than some of my options. Thanks. – Limer Mar 07 '22 at 19:58

3 Answers3

2

Here's a proof of concept that uses the following approach:

  • Make the powershell call invoke another, aux. powershell instance as the elevated target process.

  • This allows the outer powershell instance to "bake" Set-Item statements that re-create the caller's environment variables (which the outer instance inherited, and which can therefore be enumerated with Get-ChilItem Env:) into the -command string passed to the aux. instance, followed by a re-invocation of the original batch file.

Caveat: This solution blindly recreates all environment variables defined in the caller's process in the elevated process - consider pre-filtering, possibly by name patterns, such as by a shared prefix; e.g., to limit variable re-creation to those whose names start with foo, replace Get-ChildItem Env: with Get-ChildItem Env:foo* in the command below.

@echo off & setlocal

:: Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED 

:: Set sample env. vars. to pass to the elevated re-invocation.
set foo1=bar
set "foo2=none      done"
set foo3=3" of snow
:: " dummy comment to fix syntax highlighting
:: Helper variable to facilitate re-invocation.
set "thisBatchFilePath=%~f0"

:: Re-invoke with elevation, synchronously, reporting the exit
:: code of the elevated run.
:: Two sample arguments, ... and "quoted argument" are passed on re-invocation.
powershell -noprofile -command ^
  trap { [Console]::Error.WriteLine($_); exit -667 } ^
  exit ( ^
    Start-Process -Wait -PassThru -Verb RunAs powershell ^
      "\" -noprofile -command `\" $(Get-ChildItem Env: | ForEach-Object { 'Set-Item \\\"env:' + $_.Name + '\\\" \\\"' + $($_.Value -replace '\""', '`\\\""') + '\\\"; ' }) cmd /c '\`\"%thisBatchFilePath:'=''%\`\" ... \`\"quoted argument\`\" & exit'; exit `$LASTEXITCODE`\" \"" ^
  ).ExitCode 

echo -- Elevated re-invocation exited with %ERRORLEVEL%.

:: End of non-elevated part.
exit /b

:ELEVATED

echo Now running elevated...

echo -- Arguments received:
echo [%*]

echo -- Env. vars. whose names start with "foo":
set foo 

:: Determine the exit code to report.
set ec=5

echo -- Exiting with exit code %ec%...
:: Pause, so you can inspect the output before exiting.
pause
exit /b %ec%

Note:

  • trap { [Console]::Error.WriteLine($_); exit -667 } handles the case where the user declines the elevation prompt, which causes a statement-terminating error that the trap statement catches (using a try / catch statement around the Start-Process call is also an option, and usually the better choice, but in this case trap is syntactically easier).

  • Specifying pass-through arguments (arguments to pass directly to the re-invocation of the (elevated) batch file, after the cmd /c '\`\"%thisBatchFilePath:'=''%\`\" part above):

    • If arguments contain ', you must double them ('')
    • If arguments need double-quoting, you must enclose them in '\`\"...\`\" (sic), as shown with \`\"quoted argument\`\" above.
  • The cmd /c '<batch-file> & exit' re-invocation technique is required to ensure robust exit-code reporting, unfortunately - see this answer for details.

  • The explicit exit $LASTEXITCODE statement after the batch-file re-invocation is required to make the PowerShell CLI report the specific exit code reported by the batch file - without that, any nonzero exit code would be mapped to 1. See this answer for a comprehensive discussion of exit codes in PowerShell.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • This seems like a nice solution (except for the constraints) but I have some questions / require clarifications: 1. The `^` are just there so you could break up the single line into three? 2. Why `-noexit` with the 2nd Powershell? 3. Shouldn't the `foreach` be `foreach-object`? I guess it iterates over every line in `$input`, splits each one at the first(?) `=` into `$n` & `$v` and uses them to `set-Item env:...` but `foreach` cant process piped input 4. Everything after the first `^` is `ArgList` for PS#1 and after the second only for PS#2? – Limer Mar 07 '22 at 19:51
  • 1
    1: Yes, the `^` are solely for readability. 2: You can omit `-noexit` in your solution if you want the elevated session to auto-close - I put it there so that the sample output can be inspected. 3: `foreach` is an _alias_ of `ForEach-Object`; I kept it short, you can make it even shorter with another built-in alias, `%`, which you must escape as `%%`, however. Yes, the `-split '=', 2` effectively means splitting at the _first_ `=` - it requests (at most) _two_ result tokens. Re 4: yes; the `\"...\"` after the second `^` is the `-ArgumentList` argument for `Start-Process`. – mklement0 Mar 07 '22 at 19:56
  • Thanks for the clarifications - guess I'll play around with all that but `foreach` being an alias for `ForEach-Object` confuses me because it's already a different [statement]https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_foreach?view=powershell-7.2 ?! This is pretty much the first time I do something with Powershell so I've no idea what `pwsh` even is and I want to be as backward compatible as possible. Had to look up the `-c` myself so I proposed that edit. Did you accept that edit yourself alone or was another external vote required (just curious)? – Limer Mar 07 '22 at 20:09
  • @Limer, please see my update, which eliminates the previous limitations and leaves only one (originally unstated), which I think makes the solution reasonably robust (you can descend further into escaping hell if you need to fix this limitation). To avoid confusion, I've changed `foreach` to `ForEach-Object` - yes, it is confusing that `foreach` is - contextually either a distinct _language statement_, or an alias for the `ForEach-Object` _cmdlet_. I accepted the edit myself, though I didn't pay attention whether additional votes were present / are required. – mklement0 Mar 07 '22 at 20:42
  • @Limer, `pwsh.exe` is the executable of the cross-platform, install-on-demand [PowerShell (Core) 7+](https://github.com/PowerShell/PowerShell/blob/master/README.md) edition ( built on .NET (Core) 5+), which contrasts with `powershell.exe`, the executable of the legacy, ships-with-Windows, Windows-only, no longer actively developed Windows PowerShell edition (built on .NET Framework). – mklement0 Mar 07 '22 at 20:44
  • P.S., @Limer: My guess is that a post's owner can accept an edit unilaterally. – mklement0 Mar 07 '22 at 20:48
  • **RE:** `unilaterlally accepted edit` - That is/was my assumption but I wanted to use this chance to maybe figure out for sure. **RE:** `Accepting as correct answer` - I know how that works and haven't forgotten but I wanted to propose an edit because you've kinda' _only_ answered the main part of my _not directly asked_ original question (errorlevel isn't passed back through). And I have a _new_ **idea**: `(set foo) | PS -c Start-P. -verb RunAs PS \" -c Write-Output base64($input) | ?debase64? | &'%~f0' \"` whereas `?debase64?` needs to be done in the elevated PS. .bat can use stdin – Limer Mar 09 '22 at 15:09
  • @Limer, Instead of piping `set foo`, use `Get-ChildItem Env:foo*` in the PowerShell command string (the answers mentions that). This is not only simpler, but also more robust, as it correctly handles values with embedded newlines. As for Base64 encoding: I've updated the answer to remove the last remaining limitation (whitespace normalization), via careful escaping. With that, I think the need for Base64 encoding goes away. – mklement0 Mar 09 '22 at 15:59
  • @Limer, I've also updated the answer to make the elevated invocation report the exit code, which required adding `-Wait` and `-PassThru` to the `Start-Process` call, and an `exit` statement based on the returned process-info object's `.ExitCode` property. Additionally, robustly passing a batch file's exit code through requires calling via `cmd /c \`& exit`, as explained in [this answer](https://stackoverflow.com/a/55290133/45375), and, on the PowerShell side, `exit $LASTEXITCODE` is then required to report that exit code as that of the entire `-command` call. – mklement0 Mar 09 '22 at 16:02
  • @Limer, yes, there was a bug with respect to batch-file paths with embedded spaces, which I've since fixed. Please see my update, which provides additional background information, including how to escape pass-through arguments. If something is still broken, let me know. – mklement0 Mar 09 '22 at 17:56
  • 1
    Thanks. Works perfectly now:```powershell -nol -nop -c try {$p=Start-Process -Wait -PassThru -Verb RunAs powershell "\"-nol -nop -c `\"$(Get-ChildItem Env:foo*,Env:bar* | ForEach-Object {'Set-Item \\\"env:' +$_.Name +'\\\" \\\"' +$($_.Value -replace '\""', '`\\\""') +'\\\";'}); &\`\"%~f0\`\" '\`\"quoted Arg\`\"'; exit `$LastExitCode`\"\""; exit $p.ExitCode}catch{write-host $Error; exit -667}``` (missed the canceled elevation prompt) Already knew about `passthru`+`exitcode` etc.. But look at the command line of the elevated PS.exe with eg. ProcExplorer ... that doesn't look healthy. `:-)` – Limer Mar 09 '22 at 21:25
  • Glad to hear it, @Limer. Good point re handling a canceled elevation prompt - I've updated the answer with a `trap` alternative (seemed syntactically easier in this case). Yes, it's an unholy quoting and escaping mess... – mklement0 Mar 09 '22 at 21:47
  • Your current version doesn't work with `'` or ` ` in the path anymore. ```&\`\"%~f0\`\" 'nqA';``` works but ```cmd /c '\`\"%~f0\`\" nqA & exit';``` doesn't and I cant figure out why (the _dynamic command line_ looks ok). And it fails even before that with `Get-ChildItem Env:` but works with `Get-ChildItem Env:foo*` (but my env. looks fine too, nothing unusual there. **this isn't important**). Wouldn't mind the `cmd` fail because I always `exit/b N` but I need `cmd /U`... - - RE: "syntactically easier" (just) because you can still linebreak`^`? Because the "try-catch" syntax is better imho. - - – Limer Mar 15 '22 at 21:55
  • @Limer, as for `trap` vs. `try` / `catch`: the latter is generally preferable, as noted in the answer, and if you don't mind the nesting it requires, which complicates _multi-line_ formulations in a CLI call, go for it - especially if you don't mind using an - invariably lengthy and unwieldy - _single-line_ formulation. – mklement0 Mar 15 '22 at 22:21
  • @Limer, yes, due to switching to `cmd /c '...'`, a `%~f0` value that has embedded `'` must also have those escaped as `''`. – mklement0 Mar 15 '22 at 22:23
  • @Limer, I've updated the answer to handle the `'`-escaping in the batch-file path automatically, via additional helper variable `%thisBatchFilePath%` - however, any `'` in _arguments_ still require manual escaping as `''` – mklement0 Mar 15 '22 at 22:40
  • @Limer, if there are remaining problems with `Get-ChildItem Env:`, please detail them. Ditto for "doesn't work with `'` or \` \`" - it's not clear what you meant by the latter of the two. – mklement0 Mar 15 '22 at 23:09
  • a whitespace in (path of) `%~f0` still borks the script. But I've finally figured out a/the solution: ```cmd /c ' \`\"%thisBatchFilePath:'=''%\`\" ... & exit';``` - with an extra whitespace between `cmd /c '` and `%~f0`'s escaping. I'd guess the previously resulting `""` in the _dynamic command line_ was kinda escaping to one `"` and `" "` is correctly interpreted by `cmd` (or whatever). - - RE _"Get-.. Env:"_ - when my whole env is parsed PS complains about the `&` in `& exit;` -> cmd doesn't get re-invoked nor the `exit $lastexitcode`. But I don't care about that (have an _outside_ solution) – Limer Mar 15 '22 at 23:40
  • @Limer, with the `%thisBatchFilePath%` solution shown in the answer now, I don't see a problem with whitespace in the file path, on my Windows 10 20H2 machine - it works as-is (no need for adding a space after the opening `'`, which makes no difference). You can verify that - strangely - `cmd.exe` doesn't have a problem with this kind of quoting by executing the following directly from a `cmd.exe` prompt: `cmd /c ""net.exe" user"`. Similarly, I don't see a problem with `Get-ChildItem Env:`. However, we don't necessarily need to resolve these discrepancies, if you're happy with your solution,. – mklement0 Mar 16 '22 at 02:23
  • I don't _see_ it either but my `%~f0` contains one whitespace in the path part and CMD complains that it cant find the executable `""`. But you're right, such a command works on a normal CMD prompt. - I'm still running Win8.1 (with ~Win2k/7 look of course) and maybe not the latest Powershell version. - Same with the `Get... Env:`, I can't find/see the problem either but when I let it fetch my default env. the script fails. I've actually just posted my _own_ answer - so no need to fix that. – Limer Mar 16 '22 at 06:46
2

A piping hot solution

Derived from mklement0's working proof of concept and Jeroen Mostert's Base64 suggestion I've built a solution with this approach:

  • Pipe in data from inside the batch to the outer Powershell.
  • Let it convert the piped data into a Base64 string.
  • which is passed on into the command line of the elevated Powershell.
  • which in turn converts it back and pipes it into the new batch's instance.

It is more flexible because you're not limited to environment variables (you can essentially pass on anything (text based)) and the Powershell command doesn't need to be edited to choose what gets piped through. But it has a few limitations mklement0's implementation doesn't suffer from:

  • Environment variables containing newlines will not be passed on correctly and can cause chaos (depending on what comes after the LF, see barz).
  • Currently every line piped through (except for the first one) gets one whitespace prepended to it (so far I couldn't figure out how to fix that). It's usually not a problem and can be worked around (fooDoubleQouting is a negative example).
  • the elevated instance doesn't react to console input as usual any more (see notes).

Example / test batch:

@echo off & setlocal EnableDelayedExpansion

::# Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED

(set LF=^
%=this line is empty=%
)
::# Set sample env. vars. to pass to the elevated instance.
set foo1=bar
set "foo2=none done"
set foo3=3" of snow
set "barz=  Line1!LF!  foo1=Surprise^! foo1 isn't '%foo1%' anymore. It was unintentionally overwritten."
set barts=1 " 3_consecutive_" "_before_EOL   
set "barl=' sdfs' ´´`` =43::523; \/{[]} 457wb457;; %%%^!2^!11^!^!"
::# ' dummy comment#1 to fix syntax highlighting.

::# Helper variable to facilitate re-invocation (in case %~f0 contains any single quotes).
set "_selfBat=%~f0"

::# DDE - so "!" don't get expanded anymore. Was only needed for "set barz=..."
setlocal DisableDelayedExpansion
::# print variables etc. to console before self invocation & elevation.
call :testPrint

::# Generate pipe input. Be aware of CMD's handicaps of whats allowed in a command block.
::# eg. "REM" is not allowed and neither is echoing an unescaped closing parenthesis: ")" -> "^^^)"
(
    echo[foo_Setting_one=extra-varval.
    set ^^"
    echo[bar_stuff=in between.^^^)^^^"
    set bar
    echo["fooDoubleQouting=testertest"
) | powershell.exe -nologo -noprofile -command ^
    trap { [Console]::Error.WriteLine($_); exit -667 } ^
    exit ( ^
        Start-Process -PassThru -Wait -WindowStyle Maximized -Verb RunAs 'powershell.exe' ^
            "\"-nol -nop -comm `\" $('Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\\\"' + $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($(foreach ($i in $input) {\"$i`n\"})))) + '\\\")))') | cmd.exe '/D','/U','/T:4F','/S','/C',' \`\"%_selfBat:'=''%\`\" \`\"quoted argument\`\" nonQtdArg & exit'; exit `$LastExitCode `\" \"" ^
    ).exitCode

echo[
echo[ ---- Returned errorlevel is: %ERRORLEVEL%
pause
endlocal & endlocal & exit /b %ERRORLEVEL%


:testPrint
    echo[
    echo[ ---- WhiteSpaceTest: "%barts%"
    set foo
    set bar
    echo[
    set ^"
exit /B
::# " dummy comment#2 to fix syntax highlighting again.

:ELEVATED
setlocal DisableDelayedExpansion
    ::# Read and parse piped in data.
    ::# (with "delims" & "eol" truly defined as empty so every line is read as-is, even empty lines)
    for /F delims^=^ eol^= %%A in ('findstr.exe "^"') do (
        echo[ Parsing %%~A
        for /F "tokens=1,* delims=="eol^= %%B in ("%%~A") do (
            echo[   into "%%~B"
            echo[     equals "%%~C"
            ::# Convert the piped in data back into environment variables+values.
            set "%%~B=%%~C" 2>NUL
        )
        echo[
    )
    echo[-------- END PIPEREADING --------
    echo[-- Arguments received:
    echo[ [%*]

    call :testPrint

    set "ERR=42"
    echo[
    ::# to actually pause and/or wait for / react to user input(!) one needs to pipe in CON (console).
    <con set /P ERR=Enter arbitrary exitcode / errorlevel: 
endlocal & exit /B %ERR%

Notes:

  • see mklement0's notes.
  • The CMD /C '<batch-file_withEscaped'> & exit' re-invocation technique isn't required if you consistently exit /b X in your batch file. Then &\`\"%_selfBat%\`\" instead of CMD /C ... & exit is enough (with separately separated arguments: 'arg1','arg2').
  • '/D','/T:4F', - Ignore CMD's registry AutoRun commands and set fore-/background colors to white on dark red.
  • echo[ instead of echo is safer and quicker (cmd doesn't need to search for actual executables named echo.*).
  • <con is required in the elevated instance for anything needing user interaction (eg. pause or set /P ...). Without <con the now empty(?) piped in standard input (pipe#0) still delivers nul(?) to anything asking for it (my assumption). I'm sure there is a way too rescue stdin from the pipe and reattach it to con (maybe some kinde of breakthrou from in here).
  • barl's backticks get mangled.

Escaping hell

Here are the dynamic middle and inner command lines to show whats going on and shave away some escaping magic:

powershell.exe -nol -nop -comm "Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"<<BASE64_BLOB>>\"))) | cmd.exe '/D','/U','/T:4F','/S','/C',' \"<<path\this.cmd_withEscaped'>>\" \"quoted argument\" nonQtdArg & exit'; exit $LastExitCode"

Even with just my default environment that command line is somewhere around 5kB big(!), thanks to the <<BASE64_BLOB>>.

cmd.exe /D /U /T:4F /S /C " "<<path\this.cmd_withNormal'>>" "quoted argument" nonQtdArg & exit"
Limer
  • 164
  • 1
  • 10
1

So yeah, like has been said, the environment is not meant to be passed from a user to another, by design, because of the security implications. It doesn't mean that it can't be done, even if it's not something you're "supposed" to do. While I do think you should look into what do you actually want to achieve, I absolutely hate the type of answers where people tell you what you achsually "should" do and not answer the actual question at all. So I'm giving you both options here.

"Passing" the environment

You have several options here

  1. From the elevated child process, read the environment variables from the unelevated caller parent process' memory using the NtQueryInformationProcess and ReadProcessMemory APIs.

    Then either overwrite the variables on the target process (in your case, the current process) with WriteProcessMemory or just set them as you normally would. You can achieve this with only Powershell, albeit you need to add some C# code to call the required API functions.

    Here in my Enable-Privilege.ps1 example you can see how to implement NtQueryInformationProcess in PowerShell and manipulate a parent process (I wrote it for the purpose of modifying privilege tokens inside self/parent/any process). You would actually want to use a part of it because you need to enable SeDebugPrivilege to be able to manipulate memory of other processes.

    This option would probably be most "clean" and robust solution without any instantly obvious caveats. See this codeproject article for more information: Read Environment Strings of Remote Process

  2. Inside the unelevated parent process, iterate through all the environment variables and write them as a string to a single environment variable. Then pass that single environment value as an argument when spawning the elevated child process, where you can parse that string and write those environment values back. You would likely run to the same caveats as option 3 here though.

  3. Pipe the variables from the parent to the child, like has been proposed here already. The problem here is that the batch processor is really finicky and the rules of parsing and escaping are super janky, so it's very likely you would run to issues with special characters and other similar caveats with this option.

  4. Using a kernel-mode driver, overwrite the security token of the unelevated process with a elevated one, writing back the original token after you are done. On the surface this would seem like the perfect solution, since you could actually stay inside the previously-unelevated process and retain it's environment without changing context, only the security context would be replaced. As in kernel-mode you can modify everything, the security tokens are simple memory structs inside kernel memory which you can change. The problem with this approach is that it completely bypasses the windows security model, as it's supposed to be impossible to change the security token of an existing process. Because it's supposed to be "impossible", it goes deep into undocumented territory and inside the kernel you can easily break stuff if you don't know what you're doing, so this is definitely the "advanced" option (even though this particular thing is not too complicated, it's basically just writing some memory). As it's something you're not supposed to be doing, there is a possibility it breaks something since Windows does not expect a process to suddenly have a different security context. That being said, I've used this approach with no problems in the past. It could be broken in the future though by any change in the security design. You would also need to either enable testsigning (aka Disable Driver Signature Enforcement), digitally sign your driver or use some other method to bypass this requirement (f.ex. through a hypervisor or an exploit), but that is out of the scope of this answer.

The achsually version

"because circumventing the problem is no proper solution."

In this case, I would do exactly that. Since your problem is of such nature that a easy solution for it doesn't exist, because it's not supported by design. It's hard to propose a specific solution since the lack of information of what it is you're actually trying to achieve here.

I'm gonna try to cover this in a general sense. First is to think about the what it is you're actually trying to achieve here part. What are the operations you need to do which require elevation? There would be multiple ways to achieve whatever it is in a supported fashion.

Examples:

  • For whatever you need to read/write/modify, you could change the security security settings of the target (instead of the source). Meaning that let's say you need to access a specific registry key, service, file, folder, whatever, you could simply modify the ACL of the target to allow the source (i.e. the user) to do whatever operation you need. If you need to modify a single service for example, you could add the start/stop/modify right for only that single process.

  • If the thing you need is specific to the types of operations rather than specific targets, you could add the required privileges to the "Users" group. Or make a new group with the required privileges, and then add the user to that group.

  • If you want more granular control on what can/can't be done and/or the operations are specific, you could write a simple program and run it as a elevated service. Then you could just tell that service to do the required operations from the unelevated batch script, so no requesting elevation and spawning new process would be needed. You could simply do my-service.exe do-the-thing from batch, and that my-service would do the operation you need.

  • You could also always ask for the elevation in beginning of the script, but as it's clear you don't want to do this with full administrator rights, you could create a new user for just this purpose which you add to a new group for it which has the required privileges you need. Note that without resorting to the aforementioned kernel-mode ""hacks"", you cannot add new privileges for a user on-the-fly, only enable/disable/remove existing ones. What you can do though is add them beforehand based on what you need, but that will need to happen before the process is started.

anzz1
  • 91
  • 1
  • 4