#include-once

; #INDEX# ==========================================================================================================================================================
; Title .........: _SystemElevate.au3
; AutoIt Version : 3.3.6++
; Language ......: English
; Description ...: Allow SYSTEM processes to start tasks as logged on user
; ==================================================================================================================================================================

; #CURRENT# ========================================================================================================================================================
; _RunProcessAsUser
; _RunWaitProcessAsUser
; _ImpersonateUserStart
; _ImpersonateUserEnd
; ==================================================================================================================================================================

; #INTERNAL_USE_ONLY#===============================================================================================================================================
; __GetEnvironmentBlock
; ==================================================================================================================================================================


; #FUNCTION# =======================================================================================================================================================
; Name...........: _RunProcessAsUser
; Description ...: Creates a process as the user of a currently running process in the specified session.
; Syntax.........: _RunProcessAsUser($sCmdLine, $sProcessAsUser)
; Parameters ....: $sCmdLine - Full command to launch the process, including any command line parameters
;				   $sProcessAsUser - Name of a process running under the desired user's session / account. Default is explorer.exe
; Requirement(s).: Administrative rights on the target computer.
; Return values .: Success - PID of started process
;					Set Error to 0
;                  Failure - 0
;					Set Error to 1
; Author ........: Erik Pilsits
; Modified.......: Eduard Sauer
; Remarks .......:
; Related .......:
; Link ..........: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682429%28v=vs.85%29.aspx
; Example .......:
; ==================================================================================================================================================================

Func _RunProcessAsUser($sCmdLine, $sCurrentDirectory = "", $sProcessAsUser = "explorer.exe")
	Local Const $MAXIMUM_ALLOWED = 0x02000000
	Local Const $TOKEN_DUPLICATE = 0x2
	Local Const $tagSTARTUPINFO = "dword cb;ptr lpReserved;ptr lpDesktop;ptr lpTitle;dword dwX;dword dwY;dword dwXSize;dword dwYSize;" & _
									"dword dwXCountChars;dword dwYCountChars;dword dwFillAttribute;dword dwFlags;ushort wShowWindow;" & _
									"ushort cbReserved2;ptr lpReserved2;ptr hStdInput;ptr hStdOutput;ptr hStdError"
	Local Const $tagPROCESSINFO = "ptr hProcess;ptr hThread;dword dwProcessId;dword dwThreadId"
	Local Const $SecurityIdentification = 1
	Local Const $TokenPrimary = 1
	Local Const $NORMAL_PRIORITY_CLASS = 0x00000020
	Local Const $CREATE_NEW_CONSOLE = 0x00000010
	Local Const $CREATE_UNICODE_ENVIRONMENT = 0x00000400

	Local $dwSessionId, $pEnvBlock
	Local $aProcs, $processPID = -1, $ret
	Local $hProc, $hToken, $hDupToken
	Local $SI, $PI
	Local $dwCreationFlags = BitOR($NORMAL_PRIORITY_CLASS, $CREATE_NEW_CONSOLE)
	Local $sDesktop = "winsta0\default"
	Local $lpDesktop, $lpCurrentDirectory
	Local $sCmdLineType, $sCurrentDirectoryType, $iPID

	If $sCurrentDirectory <> "" Then
		$sCurrentDirectoryType = "wstr"
	Else
		$sCurrentDirectoryType = "ptr"
		$sCurrentDirectory = 0
	EndIf

	; launch as current user
	; get current session id
	$dwSessionId = DllCall("kernel32.dll", "dword", "WTSGetActiveConsoleSessionId")
	If @error Or $dwSessionId[0] = 0xFFFFFFFF Then
		SetError(1)
		Return 0
	EndIf
	$dwSessionId = $dwSessionId[0]

	; get PID of process in current session
	$aProcs = ProcessList($sProcessAsUser)
	For $i = 1 To $aProcs[0][0]
		$ret = DllCall("kernel32.dll", "int", "ProcessIdToSessionId", "dword", $aProcs[$i][1], "dword*", 0)
		If Not @error And $ret[0] And ($ret[2] = $dwSessionId) Then
			$processPID = $aProcs[$i][1]
			ExitLoop
		EndIf
	Next
	If $processPID = -1 Then
		SetError(1)
		Return 0
	EndIf ; failed to get explorer PID in current session

	; open explorer process
	$hProc = DllCall("kernel32.dll", "ptr", "OpenProcess", "dword", $MAXIMUM_ALLOWED, "int", 0, "dword", $processPID)
	If @error Or Not $hProc[0] Then
		SetError(1)
		Return 0
	EndIf
	$hProc = $hProc[0]

	; open process token
	$hToken = DllCall("advapi32.dll", "int", "OpenProcessToken", "ptr", $hProc, "dword", $TOKEN_DUPLICATE, "ptr*", 0)
	If @error Or Not $hToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		Return 0
	EndIf
	$hToken = $hToken[3]

	; duplicate token
	$hDupToken = DllCall("advapi32.dll", "int", "DuplicateTokenEx", "ptr", $hToken, "dword", $MAXIMUM_ALLOWED, "ptr", 0, _
								"int", $SecurityIdentification, "int", $TokenPrimary, "ptr*", 0)
	If @error Or Not $hDupToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		SetError(1)
		Return 0
	EndIf
	$hDupToken = $hDupToken[6]

	; get environment block
	$pEnvBlock = __GetEnvironmentBlock("explorer.exe", $dwSessionId) ; logged on user
	; create new process in user's session, with user's environment block
	If $pEnvBlock Then $dwCreationFlags = BitOR($dwCreationFlags, $CREATE_UNICODE_ENVIRONMENT)
	$SI = DllStructCreate($tagSTARTUPINFO)
	DllStructSetData($SI, "cb", DllStructGetSize($SI))
	$PI = DllStructCreate($tagPROCESSINFO)
	$lpDesktop = DllStructCreate("wchar[" & StringLen($sDesktop) + 1 & "]")
	DllStructSetData($lpDesktop, 1, $sDesktop)
	DllStructSetData($SI, "lpDesktop", DllStructGetPtr($lpDesktop))
	$ret = DllCall("advapi32.dll", "int", "CreateProcessAsUserW", "ptr", $hDupToken, "ptr", 0, "wstr", $sCmdLine, "ptr", 0, "ptr", 0, "int", 0, _
					"dword", $dwCreationFlags, "ptr", $pEnvBlock, $sCurrentDirectoryType, $sCurrentDirectory, "ptr", DllStructGetPtr($SI), "ptr", DllStructGetPtr($PI))
	If Not @error And $ret[0] Then
		;New process created successfully
		$iPID = DllStructGetData($PI, "dwProcessId")
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", DllStructGetData($PI, "hThread"))
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", DllStructGetData($PI, "hProcess"))
		$ret = $iPID
	Else
		$ret = 0
	EndIf
	If $pEnvBlock Then DllCall("userenv.dll", "int", "DestroyEnvironmentBlock", "ptr", $pEnvBlock)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hDupToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
	If $ret = 0 Then
		SetError(1)
	Else
		SetError(0)
	EndIf
	Return $ret
EndFunc   ;==>_RunProcessAsUser

; #FUNCTION# =======================================================================================================================================================
; Name...........: _RunWaitProcessAsUser
; Description ...: Creates a process as the user of a currently running process in the specified session and wait until the process is finished.
; Syntax.........: _RunWaitProcessAsUser($sCmdLine, $sProcessAsUser)
; Parameters ....: $sCmdLine - Full command to launch the process, including any command line parameters
;				   $sProcessAsUser - Name of a process running under the desired user's session / account. Default is explorer.exe
; Requirement(s).: Administrative rights on the target computer.
; Return values .: Success - 1
;					Set Error to 0
;                  Failure - 0
;					Set Error to 1
; Author ........: Erik Pilsits
; Modified.......: Eduard Sauer
; Remarks .......:
; Related .......:
; Link ..........: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682429%28v=vs.85%29.aspx
; Example .......:
; ==================================================================================================================================================================

Func _RunWaitProcessAsUser($sCmdLine, $sCurrentDirectory = "", $sProcessAsUser = "explorer.exe")
	Local Const $MAXIMUM_ALLOWED = 0x02000000
	Local Const $TOKEN_DUPLICATE = 0x2
	Local Const $tagSTARTUPINFO = "dword cb;ptr lpReserved;ptr lpDesktop;ptr lpTitle;dword dwX;dword dwY;dword dwXSize;dword dwYSize;" & _
									"dword dwXCountChars;dword dwYCountChars;dword dwFillAttribute;dword dwFlags;ushort wShowWindow;" & _
									"ushort cbReserved2;ptr lpReserved2;ptr hStdInput;ptr hStdOutput;ptr hStdError"
	Local Const $tagPROCESSINFO = "ptr hProcess;ptr hThread;dword dwProcessId;dword dwThreadId"
	Local Const $SecurityIdentification = 1
	Local Const $TokenPrimary = 1
	Local Const $NORMAL_PRIORITY_CLASS = 0x00000020
	Local Const $CREATE_NEW_CONSOLE = 0x00000010
	Local Const $CREATE_UNICODE_ENVIRONMENT = 0x00000400

	Local $dwSessionId, $pEnvBlock
	Local $aProcs, $processPID = -1, $ret
	Local $hProc, $hToken, $hDupToken
	Local $SI, $PI
	Local $dwCreationFlags = BitOR($NORMAL_PRIORITY_CLASS, $CREATE_NEW_CONSOLE)
	Local $sDesktop = "winsta0\default"
	Local $lpDesktop, $lpCurrentDirectory
	Local $sCmdLineType, $sCurrentDirectoryType, $iPID, $iExitCode

	If $sCurrentDirectory <> "" Then
		$sCurrentDirectoryType = "wstr"
	Else
		$sCurrentDirectoryType = "ptr"
		$sCurrentDirectory = 0
	EndIf

	; launch as current user
	; get current session id
	$dwSessionId = DllCall("kernel32.dll", "dword", "WTSGetActiveConsoleSessionId")
	If @error Or $dwSessionId[0] = 0xFFFFFFFF Then
		SetError(1)
		Return 0
	EndIf
	$dwSessionId = $dwSessionId[0]

	; get PID of process in current session
	$aProcs = ProcessList($sProcessAsUser)
	For $i = 1 To $aProcs[0][0]
		$ret = DllCall("kernel32.dll", "int", "ProcessIdToSessionId", "dword", $aProcs[$i][1], "dword*", 0)
		If Not @error And $ret[0] And ($ret[2] = $dwSessionId) Then
			$processPID = $aProcs[$i][1]
			ExitLoop
		EndIf
	Next
	If $processPID = -1 Then
		SetError(1)
		Return 0
	EndIf ; failed to get explorer PID in current session

	; open explorer process
	$hProc = DllCall("kernel32.dll", "ptr", "OpenProcess", "dword", $MAXIMUM_ALLOWED, "int", 0, "dword", $processPID)
	If @error Or Not $hProc[0] Then
		SetError(1)
		Return 0
	EndIf
	$hProc = $hProc[0]

	; open process token
	$hToken = DllCall("advapi32.dll", "int", "OpenProcessToken", "ptr", $hProc, "dword", $TOKEN_DUPLICATE, "ptr*", 0)
	If @error Or Not $hToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		Return 0
	EndIf
	$hToken = $hToken[3]

	; duplicate token
	$hDupToken = DllCall("advapi32.dll", "int", "DuplicateTokenEx", "ptr", $hToken, "dword", $MAXIMUM_ALLOWED, "ptr", 0, _
								"int", $SecurityIdentification, "int", $TokenPrimary, "ptr*", 0)
	If @error Or Not $hDupToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		SetError(1)
		Return 0
	EndIf
	$hDupToken = $hDupToken[6]

	; get environment block
	$pEnvBlock = __GetEnvironmentBlock("explorer.exe", $dwSessionId) ; logged on user
	; create new process in user's session, with user's environment block
	If $pEnvBlock Then $dwCreationFlags = BitOR($dwCreationFlags, $CREATE_UNICODE_ENVIRONMENT)
	$SI = DllStructCreate($tagSTARTUPINFO)
	DllStructSetData($SI, "cb", DllStructGetSize($SI))
	$PI = DllStructCreate($tagPROCESSINFO)
	$lpDesktop = DllStructCreate("wchar[" & StringLen($sDesktop) + 1 & "]")
	DllStructSetData($lpDesktop, 1, $sDesktop)
	DllStructSetData($SI, "lpDesktop", DllStructGetPtr($lpDesktop))
	$ret = DllCall("advapi32.dll", "int", "CreateProcessAsUserW", "ptr", $hDupToken, "ptr", 0, "wstr", $sCmdLine, "ptr", 0, "ptr", 0, "int", 0, _
					"dword", $dwCreationFlags, "ptr", $pEnvBlock, $sCurrentDirectoryType, $sCurrentDirectory, "ptr", DllStructGetPtr($SI), "ptr", DllStructGetPtr($PI))
	If Not @error And $ret[0] Then
		;New process created successfully
		$iPID = DllStructGetData($PI, "dwProcessId")
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", DllStructGetData($PI, "hThread"))
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", DllStructGetData($PI, "hProcess"))
		$ret = 1
	Else
		$ret = 0
	EndIf
	If $pEnvBlock Then DllCall("userenv.dll", "int", "DestroyEnvironmentBlock", "ptr", $pEnvBlock)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hDupToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
	ProcessWaitClose($iPID)
	$iExitCode = @extended
	If $ret = 0 Then
		SetError(1)
		Return $ret
	Else
		SetError(0)
		Return $iExitCode
	EndIf
EndFunc   ;==>_RunWaitProcessAsUser

; #FUNCTION# =======================================================================================================================================================
; Name...........: _ImpersonateUserStart
; Description ...: Impersonates the security context of the logged on user
; Syntax.........: _ImpersonateUserStart([$sProcess = 'explorer.exe'])
; Parameters ....: $sProcess - [Optional] Name of a process running under the logged on user's session / account.
; Requirement(s).: Administrative rights on the target computer.
; Return values .: Success - 1
;                  Failure - 0
; Author ........: Erik Pilsits
; Modified.......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......:
; ==================================================================================================================================================================

Func _ImpersonateUserStart($sProcess = "explorer.exe")
	Local Const $MAXIMUM_ALLOWED = 0x02000000

	Local $ret = 0
	Local $dwSession
	Local $aProcs, $processPID = -1, $ret
	Local $hProc, $hToken

	$dwSession = DllCall("kernel32.dll", "dword", "WTSGetActiveConsoleSessionId")
	If @error Or $dwSession[0] = 0xFFFFFFFF Then Return 0
	$dwSession = $dwSession[0]

	; get PID of process in current session
	$aProcs = ProcessList($sProcess)
	For $i = 1 To $aProcs[0][0]
		$ret = DllCall("kernel32.dll", "int", "ProcessIdToSessionId", "dword", $aProcs[$i][1], "dword*", 0)
		If Not @error And $ret[0] And ($ret[2] = $dwSession) Then
			$processPID = $aProcs[$i][1]
			ExitLoop
		EndIf
	Next
	If $processPID = -1 Then Return 0 ; failed to get PID

	; open process
	$hProc = DllCall("kernel32.dll", "ptr", "OpenProcess", "dword", $MAXIMUM_ALLOWED, "int", 0, "dword", $processPID)
	If @error Or Not $hProc[0] Then Return 0
	$hProc = $hProc[0]

	; open process token
	$hToken = DllCall("advapi32.dll", "int", "OpenProcessToken", "ptr", $hProc, "dword", $MAXIMUM_ALLOWED, "ptr*", 0)
	If @error Or Not $hToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		Return 0
	EndIf
	$hToken = $hToken[3]

	; impersonate the logged on user
	$ret = DllCall("advapi32.dll", "int", "ImpersonateLoggedOnUser", "ptr", $hToken)
	If @error Or Not $ret[0] Then
		;Error impersonating user
		$ret = 0
	Else
		;Successfully impersonated user
		$ret = 1
	EndIf
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
	Return $ret
EndFunc   ;==>_ImpersonateUserStart

; #FUNCTION# =======================================================================================================================================================
; Name...........: _ImpersonateUserEnd
; Description ...: Terminates the impersonation of a user
; Syntax.........: _ImpersonateUserEnd()
; Parameters ....: none
; Requirement(s).: Administrative rights on the target computer.
; Return values .: Success - 1
;                  Failure - 0
; Author ........: Erik Pilsits
; Modified.......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......:
; ==================================================================================================================================================================

Func _ImpersonateUserEnd()
	Local $ret = DllCall("advapi32.dll", "int", "RevertToSelf")
	If @error Or Not $ret[0] Then
		;Error reverting to self.
		Return 0
	Else
		;Successfully reverted to self
		Return 1
	EndIf
EndFunc   ;==>_ImpersonateUserEnd()


; ###################
; #INTERNAL_USE_ONLY#
; ###################

Func __GetEnvironmentBlock($sProcess, $dwSession)
	Local Const $MAXIMUM_ALLOWED = 0x02000000
	Local Const $dwAccess = BitOR(0x2, 0x8) ; TOKEN_DUPLICATE | TOKEN_QUERY

	Local $aProcs, $processPID = -1, $ret = 0
	Local $hProc, $hToken
	Local $pEnvBlock

	; get PID of process in current session
	$aProcs = ProcessList($sProcess)
	For $i = 1 To $aProcs[0][0]
		$ret = DllCall("kernel32.dll", "int", "ProcessIdToSessionId", "dword", $aProcs[$i][1], "dword*", 0)
		If Not @error And $ret[0] And ($ret[2] = $dwSession) Then
			$processPID = $aProcs[$i][1]
			ExitLoop
		EndIf
	Next
	If $processPID = -1 Then Return 0 ; failed to get PID

	; open process
	$hProc = DllCall("kernel32.dll", "ptr", "OpenProcess", "dword", $MAXIMUM_ALLOWED, "int", 0, "dword", $processPID)
	If @error Or Not $hProc[0] Then Return 0
	$hProc = $hProc[0]

	; open process token
	$hToken = DllCall("advapi32.dll", "int", "OpenProcessToken", "ptr", $hProc, "dword", $dwAccess, "ptr*", 0)
	If @error Or Not $hToken[0] Then
		DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
		Return 0
	EndIf
	$hToken = $hToken[3]

	; create a new environment block
	$pEnvBlock = DllCall("userenv.dll", "int", "CreateEnvironmentBlock", "ptr*", 0, "ptr", $hToken, "int", 1)
	If Not @error And $pEnvBlock[0] Then $ret = $pEnvBlock[1]

	; close handles
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hToken)
	DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
	Return $ret
EndFunc