Jump to content
Sign in to follow this  
eimhym

Taming JScript9 aka 'Chakra' with AutoItObject

Recommended Posts

eimhym

Since IE9, Microsoft use new JScript engine which powering HTML5 content - JScript9 aka Chakra that is ECMA-262 5th edition compliant and also JIT-ed! Alas, they decide that the powerfull engine to be unreachable through ScriptControl.

Though some hacks can re-enable this engine, there's more elegant way - The amazing AutoItObject to the rescue!

#include "AutoItObject.au3"
_AutoItObject_Startup()

$sCLSID_ScriptEngine = "{16D51579-A30B-4C8B-A276-0FF4DC41E755}" ; JScript9 'Chakra' CLSID
; $sCLSID_ScriptEngine = "{F414C260-6AC0-11CF-B6D1-00AA00BBBB58}" ; Old JScript CLSID

Global $engine_path
; ; You may specify the DLL path to make it run in portable way!
$engine_path = @ScriptDir & "\JScript9.DLL"
; $engine_path = @ScriptDir & "\JScript.DLL" ; Old JScript

; Initialization Script, for other script engine like VBScript or LuaScript the init script would be different.
$sInitScript = "AutoIt.SetRootObject(this);function Hello(){AutoIt.MsgBox(0, 'Script Says:', 'Hello World!'); return 'You are running ' + ScriptEngine() + ' ' + [ScriptEngineMajorVersion(), ScriptEngineMinorVersion(), ScriptEngineBuildVersion()].join('.')}"

Func GoNuts ( )

    ; Execute script's function and fetch return value
    MsgBox(0, "Script Return Value:", $_Script.Hello())

    ; Add new function
    $_Script.eval("function add(x, y){return x+y}")
    MsgBox(0, "Script function call", "Awesome," & @CRLF & @CRLF & "1 + 2 = " & $_Script.add(1, 2) & " !")

    ; etc ... Have Fun!
EndFunc

#Region    >>> ActiveScript Constants

Global Const _ ; IActiveScript
    $sIID_IActiveScript = "{BB1A2AE1-A4F9-11CF-8F20-00805F2CD064}", _
    $tagIActiveScript = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef ulong();" & _
            "Release ulong();" & _
            "SetScriptSite hresult(ptr);" & _
            "GetScriptSite hresult(ptr;ptr*);" & _
            "SetScriptState hresult(dword);" & _
            "GetScriptState hresult(dword*);" & _
            "Close hresult();" & _
            "AddNamedItem hresult(wstr;dword);" & _
            "AddTypeLib hresult(ptr;dword;dword;dword);" & _
            "GetScriptDispatch hresult(wstr;ptr);" & _
            "GetCurrentScriptThreadID hresult(dword*);" & _
            "GetScriptThreadID hresult(dword;dword*);" & _
            "GetScriptThreadState hresult(dword;dword*);" & _
            "InterruptScriptThread hresult(dword;ptr;dword);" & _
            "Clone hresult(int);"

; Credits to trancexx
If @AutoItX64 Then ; IActiveScriptParse
    Global Const $sIID_IActiveScriptParse = "{C7EF7658-E1EE-480E-97EA-D52CB4D76D17}"
Else ; 32 bit
    Global Const $sIID_IActiveScriptParse = "{BB1A2AE2-A4F9-11CF-8F20-00805F2CD064}"
EndIf
Global Const _
    $tIID_IActiveScriptParse = _AutoItObject_CLSIDFromString($sIID_IActiveScriptParse), _
    $tagActiveScriptParse = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef ulong();" & _
            "Release ulong();" & _
            "InitNew long();" & _
            "AddScriptlet long(wstr;wstr;wstr;wstr;wstr;wstr;dword*;ulong;dword;ptr;ptr);" & _
            "ParseScriptText long(wstr;ptr;ptr;ptr;dword*;ulong;dword;ptr;ptr);"
            ; NOTE:
            ;   The ptr in ParseScriptText (pstrItemName, pstrDelimiter) is used
            ;   instead of wstr because there's no other way to pass NULL parameter.
            ;   Change it back to wstr if you ever need to pass string.

Global Const _ ; IActiveScriptSite
    $sIID_IActiveScriptSite = "{DB01A1E3-A42B-11CF-8F20-00805F2CD064}", _
    $tagActiveScriptSite = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef ulong();" & _
            "Release ulong();" & _
            "GetLCID hresult(dword*);" & _
            "GetItemInfo hresult(wstr;dword;ptr;ptr);" & _
            "GetDocVersionString hresult(ptr);" & _
            "OnScriptTerminate hresult(ptr;ptr);" & _
            "OnStateChange hresult(uint);" & _
            "OnScriptError hresult(ptr);" & _
            "OnEnterScript hresult();" & _
            "OnLeaveScript hresult();"

Global Const _ ; IActiveScriptError
    $sIID_IActiveScriptError = "{EAE1BA61-A4ED-11CF-8F20-00805F2CD064}", _
    $tagActiveScriptError = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef ulong();" & _
            "Release ulong();" & _
            "GetExceptionInfo hresult(ptr);" & _
            "GetSourcePosition hresult(ptr;ptr;ptr);" & _
            "GetSourceLineText hresult(ptr);", _
    $tagEXCEPINFO = "word   wCode;" & _
                "word   wReserved;" & _
                "ptr    bstrSource;" & _
                "ptr    bstrDescription;" & _
                "ptr    bstrHelpFile;" & _
                "dword  dwHelpContext;" & _
                "ptr    pvReserved;" & _
                "ptr    pfnDeferredFillIn;" & _
                "long   scode;"

Enum _ ; SCRIPTSTATE
    $SCRIPTSTATE_UNINITIALIZED, _
    $SCRIPTSTATE_STARTED, _
    $SCRIPTSTATE_CONNECTED, _
    $SCRIPTSTATE_DISCONNECTED, _
    $SCRIPTSTATE_CLOSED, _
    $SCRIPTSTATE_INITIALIZED

Global Const _ ; SCRIPTITEM
    $SCRIPTITEM_ISVISIBLE     = 0x00000002, _
    $SCRIPTITEM_ISSOURCE      = 0x00000004, _
    $SCRIPTITEM_GLOBALMEMBERS = 0x00000008, _
    $SCRIPTITEM_ISPERSISTENT  = 0x00000040, _
    $SCRIPTITEM_CODEONLY      = 0x00000200, _
    $SCRIPTITEM_NOCODE        = 0x00000400, _
    $SCRIPTITEM_ALL           = BitOR( $SCRIPTITEM_ISSOURCE, $SCRIPTITEM_ISVISIBLE, $SCRIPTITEM_ISPERSISTENT, $SCRIPTITEM_GLOBALMEMBERS, $SCRIPTITEM_NOCODE, $SCRIPTITEM_CODEONLY )

#EndRegion <<< ActiveScript Constants

#Region    >>> Scriptable Object

Global $_AutoIt = _AutoItObject_Create()
Global $pAutoIt = _AutoItObject_IDispatchToPtr($_AutoIt) ; see GetItemInfo handler
Global $_Script ; will hold ScriptEngine's root object, where we can access script's global function

_AutoItObject_AddMethod($_AutoIt, "SetRootObject", "_AutoIt_SetRootObject")
_AutoItObject_AddMethod($_AutoIt, "MsgBox", "_AutoIt_MsgBox")

Func _AutoIt_SetRootObject ( $_Self, $_Root )
    Dim $_Script = $_Root ; the legit way is through IDispatchEx, but oh well
EndFunc ;==> _AutoIt_SetRootObject

Func _AutoIt_MsgBox ( $_Self, $iFlags, $sTitle, $sMessage )
    Return MsgBox($iFlags, $sTitle, $sMessage)
EndFunc ;==> _AutoIt_MsgBox

#EndRegion <<< Scriptable Object

; Look in registry if file not found
If NOT FileExists($engine_path) Then
    $engine_path = RegRead("HKCR\CLSID\" & $sCLSID_ScriptEngine & "\InprocServer32", "")
    If NOT FileExists($engine_path) Then Exit 1 ; Engine's DLL not found
EndIf

; Boilerplate
Global $_ActiveScript = _AutoItObject_ObjCreateEx($engine_path, $sCLSID_ScriptEngine, $sIID_IActiveScript, $tagIActiveScript, False)
If NOT IsObj($_ActiveScript) Then Exit 2 ; Could not instantiate engine, wrong CLSID ?
_AutoItObject_IUnknownAddRef($_ActiveScript)

$aCall = $_ActiveScript.QueryInterface(DllStructGetPtr($tIID_IActiveScriptParse), 0)
Global $_ActiveScriptParse = _AutoItObject_WrapperCreate($aCall[2], $tagActiveScriptParse)
If NOT IsObj($_ActiveScriptParse) Then Exit 3 ; Could not acquire ActiveScriptParse, check for 32/64 IID

Global $_ActiveScriptSite = _AutoItObject_ObjectFromDtag("_ASS_", $tagActiveScriptSite)
If @error Then Exit 4 ; Failed implementing ActiveScriptSite
$_ActiveScript.SetScriptSite(Number($_ActiveScriptSite.__ptr__))

$_ActiveScript.AddNamedItem("AutoIt", BitOR($SCRIPTITEM_ISVISIBLE, $SCRIPTITEM_NOCODE))

Global $fScriptInitialized = 0
$_ActiveScriptParse.InitNew()
$tActiveScriptParseError = DllStructCreate ($tagEXCEPINFO)
$_ActiveScriptParse.ParseScriptText($sInitScript, 0, 0, 0, 0, 0, 0, 0, DllStructGetPtr($tActiveScriptParseError))
$_ActiveScript.SetScriptState($SCRIPTSTATE_CONNECTED)
OnAutoItExitRegister("Cleanup")

If NOT IsObj($_Script) Then Exit 5 ; Initialization script error
GoNuts()

Func Cleanup ( )
    $_Script = 0
    $_ActiveScript.Close()
    _AutoItObject_IUnknownRelease($_ActiveScript)
    $_ActiveScriptParse = 0
    $_ActiveScript = 0
    $_ActiveScriptSite = 0
EndFunc ;==> Cleanup

#Region    >>> ActiveScriptSite Implementation

Func _ASS_QueryInterface ( $_Self, $pRIID, $pObj )
    #forceref $_Self, $pRIID, $pObj
    _Trace("_ASS_QueryInterface")
    Return 0x80004002 ; E_NOINTERFACE
EndFunc ;==> _ASS_QueryInterface

Func _ASS_AddRef ( $_Self )
    #forceref $_Self
    Return 1
EndFunc ;==> _ASS_AddRef

Func _ASS_Release ( $_Self )
    #forceref $_Self
    Return 1
EndFunc ;==> _ASS_Release

Func _ASS_GetLCID ( $_Self, $iLCID )
    #forceref $_Self
    $iLCID = 0x0400 ; LOCALE_USER_DEFAULT
    _Trace(StringFormat( '_ASS_GetLCID: 0x%X', $iLCID ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_GetLCID

Func _ASS_GetItemInfo ( $_Self, $sName, $iMask, $pObj, $pTypeInfo )
    #forceref $_Self, $pObj, $pTypeInfo
    Static Local _
        $SCRIPTINFO_IUNKNOWN  = 1, _
        $SCRIPTINFO_ITYPEINFO = 2

    _Trace(StringFormat( '_ASS_GetItemInfo: %s', $sName ))
    If $sName = "AutoIt" AND BitAND($iMask, $SCRIPTINFO_IUNKNOWN) Then
        _AutoItObject_IUnknownAddRef($pAutoIt)
        DllStructSetData(DllStructCreate("ptr", $pObj), 1, $pAutoIt)
        Return 0 ; S_OK
    EndIf
    If BitAND($iMask, $SCRIPTINFO_IUNKNOWN) Then DllStructSetData(DllStructCreate("ptr", $pObj), 1, 0)
    If BitAND($iMask, $SCRIPTINFO_ITYPEINFO) Then DllStructSetData(DllStructCreate("ptr", $pTypeInfo), 1, 0)
    Return 0x80004005 ; E_FAIL
EndFunc ;==> _ASS_GetItemInfo

Func _ASS_GetDocVersionString ( $_Self, $pbstrVersionString )
    #forceref $_Self, $pbstrVersionString
    DllStructSetData(DllStructCreate("byte", $pbstrVersionString), 1, 0)
    _Trace(StringFormat( '_ASS_GetDocVersionString: %s', "OK" ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_GetDocVersionString

Func _ASS_OnScriptTerminate ( $_Self, $pResult, $pExceptionInfo )
    #forceref $_Self, $pResult, $pExceptionInfo
    _Trace(StringFormat( '_ASS_OnScriptTerminate: %s', "OK" ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_OnScriptTerminate

Func _ASS_OnStateChange ( $_Self, $iState )
    #forceref $_Self
    _Trace(StringFormat( '_ASS_OnStateChange: State = %d', $iState ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_OnStateChange

Func _ASS_OnScriptError ( $_Self, $pActiveScriptError )
    #forceref $_Self, $pActiveScriptError

    Static Local _
        $tEXCEPINFO = DllStructCreate ($tagEXCEPINFO), _
        $pEXCEPINFO = DllStructGetPtr($tEXCEPINFO)

    Local $_Error = _AutoItObject_WrapperCreate($pActiveScriptError, $tagActiveScriptError)
    _AutoItObject_IUnknownAddRef($pActiveScriptError)
    $aCall = $_Error.GetExceptionInfo($pEXCEPINFO)

    If NOT $aCall[0] Then MsgBox(16, __Au3Obj_SysReadString(DllStructGetData($tEXCEPINFO, "bstrSource")), StringFormat ( _
            "Error 0x%X\r\n\r\n%s", _
            DllStructGetData($tEXCEPINFO, "scode"), _
            __Au3Obj_SysReadString(DllStructGetData($tEXCEPINFO, "bstrDescription")) _
        ))
        
    $_Error = 0
    Return 0 ; S_OK
EndFunc ;==> _ASS_OnScriptError

Func _ASS_OnEnterScript ( $_Self )
    #forceref $_Self
    _Trace(StringFormat( '_ASS_OnEnterScript: %s', "OK" ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_OnEnterScript

Func _ASS_OnLeaveScript ( $_Self )
    #forceref $_Self
    _Trace(StringFormat( '_ASS_OnLeaveScript: %s', "OK" ))
    Return 0 ; S_OK
EndFunc ;==> _ASS_OnLeaveScript

#EndRegion <<< ActiveScriptSite Implementation

Func _Trace ( $msg, $ln = @ScriptLineNumber )
    ConsoleWrite( @ScriptName & "(" & $ln & "): " & $msg & @CRLF)
EndFunc ;==> _Trace

Minimum Requirements (JScript9):

  • IE9 (for the JScript9.dll only, can be portable)
  • Win7 (works in WinPE too), x64 still untested

 

NOTE: Through IActiveScriptSite we can host not only JScript engine, but any other ActiveScripting engine like VBScript, LuaScript(JIT), ActivePerl etc.

NOTE: If you don't know the DLL path ( Specifiying dll path is now optional )
Please uncomment the first method of specifiying the dll path,

; ; Either get DLL path from registry
$engine_path = RegRead("HKCR\CLSID\" & $sCLSID_ScriptEngine & "\InprocServer32", "") ; ObjCreateInterface like
; ; Or bring along the DLL in portable way!
; $engine_path = @ScriptDir & "\JScript9.DLL"
; $engine_path = @ScriptDir & "\JScript.DLL" ; Old JScript

Thanks to trancexx for mentioning this

Update 23/09/13

Fixed ParseScriptText parameter, credits to trancexx

Added x64 support, untested

Edited by eimhym
  • Like 2

Share this post


Link to post
Share on other sites
trancexx

Excellent!

You should really pass pointer to EXCEPINFO structure as last argument for ParseScriptText. And to get it working for x64 you should define $sIID_IActiveScriptParse64. Maybe like this:

Global Const $sIID_IActiveScriptParse64 = "{c7ef7658-e1ee-480e-97ea-d52cb4d76d17}"
Global Const $sIID_IActiveScriptParse32 = "{BB1A2AE2-A4F9-11cf-8F20-00805F2CD064}"
Global Const $sIID_IActiveScriptParse = @AutoItX64 ? $sIID_IActiveScriptParse64 : $sIID_IActiveScriptParse32
... and dwSourceContextCookie parameter for $tagIActiveScriptParse should be "dword_ptr".

ParseScriptText shows that AutoIt lacks processing of Null keyword for aliased pointer types. That's why you specify "ptr" for "wstr" Null, right?

 

I wrote the same thing few days ago but without AutoItObject. Try it, it's much nicer.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
eimhym

You should really pass pointer to EXCEPINFO structure as last argument for ParseScriptText. And to get it working for x64 you should define $sIID_IActiveScriptParse64 and dwSourceContextCookie parameter for $tagIActiveScriptParse should be "dword_ptr"

 

Fixed, thank you for the hint! :thumbsup:

After re-read the doc, EXCEPINFO is indeed not optional. Though it is scriptlet related, not the script parsing error. We still have to watch IActiveScriptSite.OnScriptError for that purpose.

Null, right?

 

Yea, would be awesome if we can pass Null to wstr. I've added comment to ParseScriptText if someone ever need to pass string.

I wrote the same thing few days ago but without AutoItObject.

 

If you mean by that is Delphi's like interface and inheritance, oh well you just hit me with an idea :D

PS

I have no access to Win x64, so I'd be grateful if someone can confirm that it works in x64 too. The script hasn't tested thoroughly so I believe theres something left here and there. Any feedback welcomed :thumbsup:

Share this post


Link to post
Share on other sites
eimhym

"0" would be an empty string, while the pointer to that string is still not NULL. If AutoIt does convert it to NULL pointer (psz) automatically that would be great (or disaster?)

trancexx mentioned about keyword for NULL, IMHO that would be better.

Edited by eimhym

Share this post


Link to post
Share on other sites
trancexx

If you mean by that is Delphi's like interface and inheritance, oh well you just hit me with an idea :D

I meant by using built-in ObjCreateInterface function.

I needed to execute some online javascript code because converting it to AutoIt was too complicated. It was much easier to use IActiveScript to do it for me.

You would have troubles finding anyone to test your code because you're making it hard to do that. First of all you shouldn't be using dll directly when Windows offers easier ways for creating this object. For example this is preferred way:

ObjCreateInterface($sCLSID_ScriptEngine, $sIID_IActiveScript, $tagIActiveScript)
... or even better this:

ObjCreateInterface("JScript", $sIID_IActiveScript, $tagIActiveScript)
Edited by trancexx

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
eimhym

you shouldn't be using dll directly

 

That's why Chakra is by default inaccessible - it does have CLSID but no ProgID. The hack I mentioned in first post is about assigning ProgID manually, but that require editing the registry.

ObjCreateInterface is cool, and if anyone prefer that, please uncomment the first method of obtaining dll path from registry and comment the later (specify it directly). E.g:

; ; Either get DLL path from registry
$engine_path = RegRead("HKCR\CLSID\" & $sCLSID_ScriptEngine & "\InprocServer32", "") ; ObjCreateInterface like, autopath
; ; Or bring along the DLL in portable way!
; $engine_path = @ScriptDir & "\JScript9.DLL" ; comment this!
; $engine_path = @ScriptDir & "\JScript.DLL" ; Old JScript

For anyone courious, thats what ObjCreateInterface would do anyway; find ProgID/CLSID entry in registry, look for dll path entry.

After InprocServer path found, we instantiate class by invoking dll's exported DllGetClassObject and then CreateInstance from the class factory - and I think thats also what _AutoItObject_ObjCreateEx do. I haven't look at AutoItObject source though, trancexx you know better lol

To note again, JScript9.dll can be used even without having IE browser installed. This would be extremely usefull in WinPE environment. A portable Windows Scripting Host if I may say.

Again, if anyone not sure where the hell the dll is, please uncomment the first method like shown above

Updated: now trying to find path in registry automatically when specified dll not found

Edited by eimhym

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×