Here's what I've got so far.
Wscript.Shell
's .ShellExecute
method would let you prompt for UAC elevation. If you could break your admin-dependent functions into helper scripts and stick this BatchGotAdmin code at the top of each, that would let you run the rest in the context of a normal user; but it'd still require the user to click "Allow" for each helper script run.
On the opposing side of the coin, using Wscript.Shell
's .Exec
method to do:
runas /env /netonly /noprofile %userdomain%\%username% "command to run"
... results in bypassing the password and prompt, resulting in the command to be run unauthenticated. This is very interesting and unexpected behavior. Because the "Enter password" prompt is bypassed but the command runs in a separate console anyway, I think it runs as a normal user. However, I haven't found any worthwhile tests with which to confirm.
The problem I've run into with this is that runas
called in this way seems to be non-blocking, so it's hard to deal with the output and timing. In case it helps, I'm including my scratch pad test broken code at the bottom of this answer.
Another alternative would be to create a Scheduled Task to run your PowerShell snippet un-elevated.
There's also Wscript.Shell
's .Run method that would let you .SendKeys
the password which would let you get around the UAC prompt, but it doesn't block either, and requires you to store a password in your script.
I'm afraid I've applied all my ingenuity to the problem but haven't found any solution which doesn't create another problem -- other than possibly the Scheduled Task solution.
Here's the incomplete WshShell.Exec
solution referenced in item 2 above:
@if (@CodeSection==@Batch) @then
@echo off
setlocal
call :runAsNonAdmin "cmd /c dir"
goto :EOF
:runAsNonAdmin <command to run>
setlocal enabledelayedexpansion
cscript /nologo /e:JScript "%~f0" "%userdomain%\%username%" "%~1"
endlocal & goto :EOF
@end // end batch / begin JScript chimera
var args = {
user: WSH.Arguments(0),
cmd: WSH.Arguments(1)
},
runas = 'runas /env /netonly /noprofile /user:' + args.user + ' "' + args.cmd + '>stdout 2>stderr"',
osh = WSH.CreateObject('wscript.shell'),
fso = WSH.CreateObject('scripting.filesystemobject'),
proc = osh.Exec(runas),
read = '', file, out = ['stdout','stderr'];
// note: proc.StdOut and proc.StdErr refer *only* to the runas command itself,
// not to the command spawned by it. The spawned command is essentially sandboxed.
while (!proc.Status || !proc.StdErr.AtEndOfStream || !proc.StdOut.AtEndOfStream) {
if (!proc.StdErr.AtEndOfStream) {
WSH.StdErr.WriteLine(proc.StdErr.ReadLine());
} else if (!proc.StdOut.AtEndOfStream) {
WSH.StdOut.Write(proc.StdOut.Read(1));
}
}
for (var i in out) {
if (fso.fileExists(out[i])) {
if (fso.GetFile(out[i]).Size) {
file = fso.OpenTextFile(out[i], 1);
WSH[out[i]].Write(file.ReadAll());
file.Close();
}
var del = osh.Exec('cmd /c del ' + out[i]);
while (!proc.Status) WSH.Sleep(10);
}
}
WSH.Echo(proc.ProcessID + ': status ' + proc.Status + '; exit ' + proc.ExitCode);
WSH.Quit(0);
// Inactive code. Since .exec skips authentication, the following code results in a broken pipe error.
while (!proc.Status || !proc.StdErr.AtEndOfStream || !proc.StdOut.AtEndOfStream) {
if (!proc.StdOut.AtEndOfStream) {
read += proc.StdOut.Read(1);
if (/Enter the password for .*?:/.test(read)) {
proc.StdIn.WriteLine(args.pass);
}
} else if (!proc.StdErr.AtEndOfStream) WSH.Echo(proc.StdErr.ReadLine());
else WSH.Sleep(10);
}