AutoHotKey_Lで、プロセス間通信経由して一般権限でRunしてみた。ver.1.2
UIPIの制限を回避するため、常駐AHKは管理者権限で実行しているのだが、それゆえにRunした時に不要な特権が付与されてしまう。それを回避する手段はいくつかあるものの、どれも一長一短で、最終的に「一般権限の常駐AHKにプロセス間通信でパスを送ってRun」に行きついた。最初からそうしとけって話だが、旧人類なので常駐増やしたくないのだ!
Receive2Run.ahkを一般権限で常駐させて、管理者権限AHKからRunNEXすれば、R2Rが代わりにRunしてくれる。
"|"はEverythingにクエリ送るとき使ってたので、区切り文字はAHKらしく"`n"にした。
; 管理者権限のAHKに書くやつ
RunNEX(Cmdline, WorkDir = "", Opt = "", ByRef Pid = "") {
SetTitleMatchMode 2
DetectHiddenWindows On
sig := "RunNEX:"
SendHwnd := WinExist("Receive2Run. ahk_class AutoHotkey")
if (0 == SendHwnd) {
MsgBox, 262160, , Receive2Runが見つかりません。
return
}
if !WorkDir {
WorkDir := A_WorkingDir
}
StringToSend := sig Cmdline "`n" WorkDir "`n" Opt
Result := Send_WM_COPYDATA(StringToSend, "ahk_id " SendHwnd)
if (0 < Result) {
Pid := Result
} else {
MsgBox,Err!`n%Result%
}
}
;https://www.autohotkey.com/docs/commands/OnMessage.htm
Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle) {
PtrSize := A_PtrSize ? A_PtrSize : 4
VarSetCapacity(CopyDataStruct, 3*PtrSize, 0)
SizeInBytes := (StrLen(StringToSend) + 1) * (A_IsUnicode ? 2 : 1)
NumPut(SizeInBytes, CopyDataStruct, PtrSize)
NumPut(&StringToSend, CopyDataStruct, 2*PtrSize)
SendMessage, 0x4a, 0, &CopyDataStruct,, %TargetScriptTitle%
return ErrorLevel
}
;/////////////////////////////////////////////////////////////////////////////////////
;
; Receive2Run.ahk
;
;/////////////////////////////////////////////////////////////////////////////////////
#SingleInstance, Force
#NoEnv
#Persistent
SetBatchLines, -1
Menu, Tray, Icon, SHELL32.dll, 262
sig := "RunNEX:"
if A_IsAdmin {
MsgBox, 262160, , 管理者権限で起動しないでください。
ExitApp
}
OnMessage(0x4a, "Receive_WM_COPYDATA")
return
;https://www.autohotkey.com/docs/commands/OnMessage.htm
Receive_WM_COPYDATA(wParam, lParam) {
global sig
PtrSize:=A_PtrSize ? A_PtrSize : 4
StringAddress := NumGet(lParam + 2*PtrSize)
CopyOfData := StrGet(StringAddress)
if RegExMatch(CopyOfData, "^" sig "([^\n]+)(?:\n([^\n]*))?(?:\n([^\n]*))?(?:\n([^\n]*))?$", $) {
TrgPath := $1
WorkDir := $2
Opt := $3
TrgPid := 0
Run, %TrgPath%, %WorkDir%, %Opt%, TrgPid
} else {
MsgBox, Err!`n%CopyOfData%
}
return TrgPid
}
メッセージ受け取ってRunとかいかにも悪用されそうなので、とりあえず符丁つけてみた。使うときは双方のsigを適当に書き換えてね。
送り主を確認するとか他にやるべきことがあるのでは?という気もするが、所詮「とりあえず」なんで……。
他の手段、「一般ユーザーでRunAS」「CreateProcessWithTokenWでShellから一般権限をコピー」「Shellに引数をつけてRun」とかあるのだけど――。RunAsはパスワードを平文で記録したくないのでNG。CreateProcessWithTokenWは複雑で、フォーラムにあったのコピペで試してたけど、動作したりしなかったり。で、Shellに.lnkを実行させるハックを愛用してたのだけど、これも近頃調子悪くなってしまい、諦めて常駐増やしたっちゅー……。
オマケ
R2RがなければShellに起動してもらう差分。フォルダウィンドウをプロセス分離する設定だと、ゾンビが大量発生してしまうっぽいので、それらしきものを強制終了する(それが嫌でR2Rを作った)。
SendHwnd := WinExist("Receive2Run. ahk_class AutoHotkey")
if (0 == SendHwnd) { ; R2RがなければShellに起動してもらう
CheckTime := A_Now "." A_MSec "000+540"
Run, Explorer.exe "C:\Receive2Run.exe" ; R2Rのパスを指定
WinWait, Receive2Run., , 5
; R2R起動後に生成されたExplorerを強制終了
ExpCmd := "C:\WINDOWS\explorer.exe /factory,{75dff2b7-6936-4c06-a8bb-676a7b00b24b} -Embedding" ; 環境による?
StringReplace, ExpCmdEsc, ExpCmd, \, \\, All
queryEnum := ComObjGet("winmgmts:").ExecQuery("Select ProcessId from Win32_Process Where CommandLine='" ExpCmdEsc "' and CreationDate>='" CheckTime "'")._NewEnum()
if queryEnum[process] {
ExpPid := process.ProcessId
Process, Close, %ExpPid%
}
SendHwnd := WinExist("Receive2Run. ahk_class AutoHotkey")
if (0 == SendHwnd) {
MsgBox, 262160, , Receive2Runが見つかりません。
return
}
}
結構便利です。
RunAs_general(exe:="", arg:="", workdir:="") {
ShellExecute(exe, arg, workdir)
}
ShellExecute(Parameters*) {
ComObjCreate("Shell.Application")
.Windows
.FindWindowSW(0, 0, 8, 0, 1)
.Document
.Application
.ShellExecute(Parameters*)
}
https://gist.github.com/anonymous1184/fa660cf2312cc50b38621a7c9b23b628?permalink_comment_id=4579504#gistcomment-4579504