(pretend) No-restart UAC elevation
Posted: Sat Jul 25, 2009 2:44 pm
I say pretend because it really does involve an app (re)start.
How it works: restarts the exe with a command line parameter (defined by a constant) as admin-mode, and sends it commands via a named pipe. (debugger compatible)
It also only requires ONE UAC elevation prompt no matter how many commands are sent (unless the sub process exits abnormally or something goes wrong with the pipe) - just add a ClosePipe() after sending a command to change this.
Unicode/x64 compatible.
All you need do is extract the code from inside the start/end include file blocks in the first file, and add your commands to the client file.
In debugger mode, the client exe will emit a debugger warning, so you can tell which debugger it is (in case you use the standalone debugger for everything like me)
The tab char is used in the communication to separate the command from its parameters (see client.pb)
Comments are included (including procedure pane ones for important bits), so there should be no difficulty understanding it
Included example runs cmd from the admin mode client when the button is clicked.
Save this somehere:
Save this as client.pb in the same dir as the other file
Please let me know if you find a bug, as I (will) use it in my IDE.
How it works: restarts the exe with a command line parameter (defined by a constant) as admin-mode, and sends it commands via a named pipe. (debugger compatible)
It also only requires ONE UAC elevation prompt no matter how many commands are sent (unless the sub process exits abnormally or something goes wrong with the pipe) - just add a ClosePipe() after sending a command to change this.
Unicode/x64 compatible.
All you need do is extract the code from inside the start/end include file blocks in the first file, and add your commands to the client file.
In debugger mode, the client exe will emit a debugger warning, so you can tell which debugger it is (in case you use the standalone debugger for everything like me)
The tab char is used in the communication to separate the command from its parameters (see client.pb)
Comments are included (including procedure pane ones for important bits), so there should be no difficulty understanding it
Included example runs cmd from the admin mode client when the button is clicked.
Save this somehere:
Code: Select all
EnableExplicit
;*** begin include file ***
#pipeName = "\\.\pipe\FakeUACNoRestart"
#admin_CommandFlag = "/admin"
#pipe_maxChars = 1024;- change this if you need more than 1024 chars per command
global fconnected, Hpipe, pipe_hProcess
;Thanks to freak @ http://purebasic.fr/english/viewtopic.php?p=237513#237513 for this proc
#BCM_FIRST = $1600
#BCM_SETSHIELD = #BCM_FIRST + $000C
Procedure Button_SetElevationRequiredState(hwndButton, fRequired);Show/Hide the UAC shield icon. (set fRequired to #True or #False)
SendMessage_(hwndButton, #BCM_SETSHIELD, 0, fRequired)
EndProcedure
#SEE_MASK_UNICODE = $00004000
Procedure RunProgramAdmin(windowid, exe.s, params.s, workingdir.s);returns a handle that you must CloseHandle_()!
Protected shellex.SHELLEXECUTEINFO
shellex\cbSize = SizeOf(SHELLEXECUTEINFO)
shellex\fMask = #SEE_MASK_NOCLOSEPROCESS
CompilerIf #PB_Compiler_Unicode
shellex\fMask = shellex\fMask|#SEE_MASK_UNICODE
CompilerEndIf
shellex\hwnd = windowid
shellex\lpVerb = @"runas"
shellex\lpFile = @exe
shellex\lpParameters = @params
shellex\lpDirectory = @workingdir
shellex\nShow = #SW_SHOW
ShellExecuteEx_(shellex)
;debug shellex\hProcess
;debug Hex(shellex\hInstApp)
ProcedureReturn shellex\hProcess
EndProcedure
Procedure ProcessRunning(hProcess)
Protected exitcode.l
GetExitCodeProcess_(hProcess, @exitcode)
if exitcode = #STILL_ACTIVE
ProcedureReturn 1
else
ProcedureReturn 0
endif
EndProcedure
Procedure PipeInit(pipeHandle)
fConnected = ConnectNamedPipe_(pipeHandle, #Null)
EndProcedure
Procedure SendPipe(pipeHandle, tosend.s)
Protected sentbytes, returnval
returnval = WriteFile_(pipeHandle, tosend, StringByteLength(tosend), @sentbytes, 0)
FlushFileBuffers_(pipeHandle)
ProcedureReturn returnval
EndProcedure
Procedure ClosePipe()
if Hpipe
SendPipe(hPipe, "end")
DisconnectNamedPipe_(Hpipe)
CloseHandle_(Hpipe)
Hpipe = 0
Endif
if pipe_hProcess
CloseHandle_(pipe_hProcess)
Endif
EndProcedure
Procedure DoAdminAction(parentwindow, action.s)
Protected thid
if Hpipe and (ProcessRunning(pipe_hProcess) = 0)
ClosePipe()
DoAdminAction(parentwindow, action)
ProcedureReturn
ElseIf not Hpipe
Hpipe=CreateNamedPipe_(#pipeName, #PIPE_ACCESS_DUPLEX, #PIPE_TYPE_MESSAGE | #PIPE_READMODE_MESSAGE, 1, #pipe_maxChars* SizeOf(Character), #pipe_maxChars* SizeOf(Character), 3000, #Null)
thid = CreateThread(@PipeInit(), Hpipe)
CompilerIf #PB_Compiler_Debugger;-this will interfere with the process running checks. disable it (put "0 or" in front of the constant) to let the check work
pipe_hProcess = RunProgramAdmin(parentwindow, #PB_Compiler_Home+"compilers\pbdebuggerunicode.exe", #DQUOTE$+ProgramFilename()+#DQUOTE$+" "+#admin_CommandFlag, GetCurrentDirectory());we run the debugger, because otherwise if you start a console app, like cmd.exe, it will inherit the console debugger's console.
CompilerElse
pipe_hProcess = RunProgramAdmin(parentwindow, ProgramFilename(), #admin_CommandFlag, GetCurrentDirectory())
CompilerEndIf
fconnected = 0
if pipe_hProcess
WaitThread(thid);-instead of this you could make it timeout and call closepipe() if you dont want to wait forever.
endif
If not fConnected : MessageRequester("Error", "Could not connect pipe") : end : endif ;- End keyword here!! (remove if you don't want to exit)
endif
if Hpipe
if SendPipe(hPipe, action) = 0;would return 0 if the pipe is damaged
ClosePipe()
DoAdminAction(parentwindow, action)
endif
endif
EndProcedure
if ProgramParameter(0) = #admin_CommandFlag
IncludeFile "client.pb";- admin mode client file.
end
endif
;*** end include file ***
Define EventID, quit.i
OpenWindow(0,0,0, 400,100, "Pipe Server", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ButtonGadget(0, 50,35,300,25, "Perform Admin Action")
Button_SetElevationRequiredState(GadgetID(0), #True)
Repeat
EventID = WaitWindowEvent()
Select EventID
case #PB_Event_Gadget
DoAdminAction(WindowID(0), "cmd")
Case #PB_Event_CloseWindow
Quit = 1
EndSelect
Until Quit = 1
ClosePipe()
Code: Select all
CompilerIf #PB_Compiler_Debugger
Import ""
PB_DEBUGGER_SendWarning(Message.p-ascii)
EndImport
PB_DEBUGGER_SendWarning("I am the admin mode client instance");this is so you can tell which debugger is running the client
CompilerEndIf
define pipe_wait, pipe_file, pipe_readbuf.s, pipe_bytesread
pipe_wait=WaitNamedPipe_(#pipeName, #Null)
If pipe_wait
pipe_file = CreateFile_(#pipeName, #GENERIC_READ |#GENERIC_WRITE, 0, #Null, #OPEN_EXISTING,0, #Null)
If pipe_file
Repeat
pipe_readbuf.s=Space(#pipe_maxChars)
if ReadFile_(pipe_file , @pipe_readbuf,StringByteLength(pipe_readbuf), @pipe_bytesread, 0) = 0
CloseHandle_(pipe_file)
end
endif
Select Trim(Lcase(StringField(pipe_readbuf, 1, chr(9))))
Case "cmd"
RunProgram("cmd.exe")
case "end";DO NOT REMOVE THIS UNLESS YOU CHANGE THE EXITING METHOD.
CloseHandle_(pipe_file)
end
EndSelect
ForEver
EndIf
Endif