Jump to content

Windows 11 Jump List (Tasks)


Go to solution Solved by Nine,

Recommended Posts

Posted

I am trying to get this working but can't seem to get it or find a easier way about going about this.

Attached picture for context.

 

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

; ====== Define all necessary GUIDs ======
; CLSIDs
Global Const $CLSID_DestinationList = "{77f10cf0-3db5-4966-b520-b7c54fd35ed6}"
Global Const $CLSID_EnumerableObjectCollection = "{2d3468c1-36a7-43b6-ac24-d3f02fd9607a}"
Global Const $CLSID_ShellLink = "{00021401-0000-0000-C000-000000000046}"

; IIDs
Global Const $IID_ICustomDestinationList = "{6332debf-87b5-4673-8ff5-7ca6a2ff46a8}"
Global Const $IID_IObjectCollection = "{2d3468c1-36a7-43b6-ac24-d3f02fd9607a}"
Global Const $IID_IObjectArray = "{92ca9dcd-5622-4bba-a805-5e9f541bd8c9}"
Global Const $IID_IShellLinkW = "{000214F9-0000-0000-C000-000000000046}"
Global Const $IID_IPropertyStore = "{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}"

; Property key for title
Global Const $PKEY_Title_FMTID = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"
Global Const $PKEY_Title_PID = 2

; AppUserModelID for our application
Global Const $APPID = "AutoIt.JumpList.Example." & Random(1000, 9999, 1)

; Main GUI
Global $hGUI = GUICreate("Jump List Example", 400, 300)
GUICtrlCreateLabel("Right-click this application in the taskbar", 50, 120, 300, 30)
GUICtrlSetFont(-1, 12)
GUICtrlCreateLabel("Tasks: Notepad, Calculator, CMD", 50, 150, 300, 30)
GUISetState(@SW_SHOW)

; Initialize COM
DllCall("ole32.dll", "long", "CoInitialize", "ptr", 0)

; Set AppUserModelID for jump list
_SetAppUserModelID()

; Create jump list
_CreateJumpList()

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            ; Uninitialize COM
            DllCall("ole32.dll", "none", "CoUninitialize")
            Exit
    EndSwitch
WEnd

Func _SetAppUserModelID()
    ; Set the AppUserModelID for our process
    Local $aRet = DllCall("shell32.dll", "long", "SetCurrentProcessExplicitAppUserModelID", "wstr", $APPID)
    If @error Or $aRet[0] <> 0 Then
        ConsoleWrite("Warning: Failed to set AppUserModelID: " & Hex($aRet[0]) & @CRLF)
        Return False
    EndIf
    ConsoleWrite("AppUserModelID set to: " & $APPID & @CRLF)
    Return True
EndFunc

Func _CreateJumpList()
    ConsoleWrite("Creating jump list..." & @CRLF)

    ; Create destination list COM object
    Local $pDestList = 0
    Local $aRet = DllCall("ole32.dll", "long", "CoCreateInstance", _
        "ptr", _GUIDFromString($CLSID_DestinationList), _
        "ptr", 0, _
        "dword", 1, _ ; CLSCTX_INPROC_SERVER
        "ptr", _GUIDFromString($IID_ICustomDestinationList), _
        "ptr*", 0)

    If @error Or $aRet[0] <> 0 Then
        ConsoleWrite("Failed to create DestinationList: 0x" & Hex($aRet[0], 8) & @CRLF)
        Return False
    EndIf

    Local $pDestList = $aRet[5]
    ConsoleWrite("DestinationList created at: 0x" & Hex($pDestList) & @CRLF)

    ; Get the vtable
    Local $pVTable = DllStructGetData(DllStructCreate("ptr", $pDestList), 1)

    ; Get BeginList method (vtable index 3)
    Local $pBeginList = DllStructGetData(DllStructCreate("ptr", $pVTable + 3*4), 1)

    ; Call BeginList
    Local $cMaxSlots = 0
    Local $pRemovedArray = 0
    Local $aRet2 = DllCallAddress("long", $pBeginList, _
        "ptr", $pDestList, _
        "uint*", $cMaxSlots, _
        "ptr", _GUIDFromString($IID_IObjectArray), _
        "ptr*", 0)

    If @error Or $aRet2[0] <> 0 Then
        ConsoleWrite("BeginList failed: 0x" & Hex($aRet2[0], 8) & @CRLF)
        _ReleaseObject($pDestList)
        Return False
    EndIf

    ConsoleWrite("BeginList succeeded. Max slots: " & $cMaxSlots & @CRLF)

    ; Create object collection for tasks
    Local $pObjCollection = 0
    Local $aRet3 = DllCall("ole32.dll", "long", "CoCreateInstance", _
        "ptr", _GUIDFromString($CLSID_EnumerableObjectCollection), _
        "ptr", 0, _
        "dword", 1, _
        "ptr", _GUIDFromString($IID_IObjectCollection), _
        "ptr*", 0)

    If @error Or $aRet3[0] <> 0 Then
        ConsoleWrite("Failed to create ObjectCollection: 0x" & Hex($aRet3[0], 8) & @CRLF)
        _ReleaseObject($pDestList)
        Return False
    EndIf

    Local $pObjCollection = $aRet3[5]
    ConsoleWrite("ObjectCollection created at: 0x" & Hex($pObjCollection) & @CRLF)

    ; Create and add tasks
    Local $bSuccess = True

    ; Task 1: Notepad
    If Not _AddTaskToCollection($pObjCollection, "Open Notepad", @WindowsDir & "\notepad.exe", "", "Launch Notepad text editor", @SystemDir & "\imageres.dll", 2) Then
        ConsoleWrite("Failed to add Notepad task" & @CRLF)
        $bSuccess = False
    EndIf

    ; Task 2: Calculator
    If Not _AddTaskToCollection($pObjCollection, "Open Calculator", @WindowsDir & "\system32\calc.exe", "", "Launch Calculator", @SystemDir & "\shell32.dll", 24) Then
        ConsoleWrite("Failed to add Calculator task" & @CRLF)
        $bSuccess = False
    EndIf

    ; Task 3: Command Prompt
    If Not _AddTaskToCollection($pObjCollection, "Open CMD", @ComSpec, "/k echo Hello from Jump List", "Launch Command Prompt", @SystemDir & "\shell32.dll", 61) Then
        ConsoleWrite("Failed to add CMD task" & @CRLF)
        $bSuccess = False
    EndIf

    If Not $bSuccess Then
        ConsoleWrite("Some tasks failed to add, but continuing..." & @CRLF)
    EndIf

    ; Get AddUserTasks method (vtable index 6)
    Local $pAddUserTasks = DllStructGetData(DllStructCreate("ptr", $pVTable + 6*4), 1)

    ; Add tasks to jump list
    Local $aRet4 = DllCallAddress("long", $pAddUserTasks, _
        "ptr", $pDestList, _
        "ptr", $pObjCollection)

    If @error Or $aRet4[0] <> 0 Then
        ConsoleWrite("AddUserTasks failed: 0x" & Hex($aRet4[0], 8) & @CRLF)
        _ReleaseObject($pObjCollection)
        _ReleaseObject($pDestList)
        Return False
    EndIf

    ConsoleWrite("Tasks added to jump list" & @CRLF)

    ; Get CommitList method (vtable index 4)
    Local $pCommitList = DllStructGetData(DllStructCreate("ptr", $pVTable + 4*4), 1)

    ; Commit the jump list
    Local $aRet5 = DllCallAddress("long", $pCommitList, _
        "ptr", $pDestList)

    If @error Or $aRet5[0] <> 0 Then
        ConsoleWrite("CommitList failed: 0x" & Hex($aRet5[0], 8) & @CRLF)
    Else
        ConsoleWrite("Jump list created successfully!" & @CRLF)
        MsgBox(64, "Success", "Jump list created!" & @CRLF & "Pin this app to taskbar and right-click to see the custom tasks.")
    EndIf

    ; Cleanup
    _ReleaseObject($pObjCollection)
    _ReleaseObject($pDestList)

    Return True
EndFunc

Func _AddTaskToCollection($pCollection, $sTitle, $sTargetPath, $sArguments, $sDescription, $sIconPath, $iIconIndex)
    ConsoleWrite("Adding task: " & $sTitle & @CRLF)

    ; Create shell link COM object
    Local $pShellLink = 0
    Local $aRet = DllCall("ole32.dll", "long", "CoCreateInstance", _
        "ptr", _GUIDFromString($CLSID_ShellLink), _
        "ptr", 0, _
        "dword", 1, _
        "ptr", _GUIDFromString($IID_IShellLinkW), _
        "ptr*", 0)

    If @error Or $aRet[0] <> 0 Then
        ConsoleWrite("Failed to create ShellLink: 0x" & Hex($aRet[0], 8) & @CRLF)
        Return False
    EndIf

    Local $pShellLink = $aRet[5]
    ConsoleWrite("ShellLink created at: 0x" & Hex($pShellLink) & @CRLF)

    ; Get IShellLink vtable
    Local $pVTableSL = DllStructGetData(DllStructCreate("ptr", $pShellLink), 1)

    ; Set path (vtable index 20)
    Local $pSetPath = DllStructGetData(DllStructCreate("ptr", $pVTableSL + 20*4), 1)
    Local $aRet2 = DllCallAddress("long", $pSetPath, _
        "ptr", $pShellLink, _
        "wstr", $sTargetPath)

    If @error Or $aRet2[0] <> 0 Then
        ConsoleWrite("SetPath failed: 0x" & Hex($aRet2[0], 8) & @CRLF)
    EndIf

    ; Set arguments if provided
    If $sArguments <> "" Then
        Local $pSetArguments = DllStructGetData(DllStructCreate("ptr", $pVTableSL + 11*4), 1)
        Local $aRet3 = DllCallAddress("long", $pSetArguments, _
            "ptr", $pShellLink, _
            "wstr", $sArguments)

        If @error Or $aRet3[0] <> 0 Then
            ConsoleWrite("SetArguments failed: 0x" & Hex($aRet3[0], 8) & @CRLF)
        EndIf
    EndIf

    ; Set description
    Local $pSetDescription = DllStructGetData(DllStructCreate("ptr", $pVTableSL + 7*4), 1)
    Local $aRet4 = DllCallAddress("long", $pSetDescription, _
        "ptr", $pShellLink, _
        "wstr", $sDescription)

    If @error Or $aRet4[0] <> 0 Then
        ConsoleWrite("SetDescription failed: 0x" & Hex($aRet4[0], 8) & @CRLF)
    EndIf

    ; Set icon if provided
    If $sIconPath <> "" And FileExists($sIconPath) Then
        Local $pSetIconLocation = DllStructGetData(DllStructCreate("ptr", $pVTableSL + 17*4), 1)
        Local $aRet5 = DllCallAddress("long", $pSetIconLocation, _
            "ptr", $pShellLink, _
            "wstr", $sIconPath, _
            "int", $iIconIndex)

        If @error Or $aRet5[0] <> 0 Then
            ConsoleWrite("SetIconLocation failed: 0x" & Hex($aRet5[0], 8) & @CRLF)
        EndIf
    Else
        ConsoleWrite("Icon path not found: " & $sIconPath & @CRLF)
    EndIf

    ; Try to set title using property store (optional)
    _SetShellLinkTitle($pShellLink, $sTitle)

    ; Add object to collection
    Local $pVTableOC = DllStructGetData(DllStructCreate("ptr", $pCollection), 1)
    Local $pAddObject = DllStructGetData(DllStructCreate("ptr", $pVTableOC + 6*4), 1)

    Local $aRet8 = DllCallAddress("long", $pAddObject, _
        "ptr", $pCollection, _
        "ptr", $pShellLink)

    _ReleaseObject($pShellLink)

    If @error Or $aRet8[0] <> 0 Then
        ConsoleWrite("AddObject failed: 0x" & Hex($aRet8[0], 8) & @CRLF)
        Return False
    EndIf

    ConsoleWrite("Task added successfully: " & $sTitle & @CRLF)
    Return True
EndFunc

Func _SetShellLinkTitle($pShellLink, $sTitle)
    ; Try to set the title property (optional - shell link will still work without it)
    Local $pPropertyStore = 0
    Local $pVTableSL = DllStructGetData(DllStructCreate("ptr", $pShellLink), 1)

    ; QueryInterface for IPropertyStore (vtable index 0)
    Local $pQueryInterface = DllStructGetData(DllStructCreate("ptr", $pVTableSL), 1)
    Local $aRet = DllCallAddress("long", $pQueryInterface, _
        "ptr", $pShellLink, _
        "ptr", _GUIDFromString($IID_IPropertyStore), _
        "ptr*", 0)

    If @error Or $aRet[0] <> 0 Then
        ; ConsoleWrite("QueryInterface for IPropertyStore failed: 0x" & Hex($aRet[0], 8) & @CRLF)
        Return False
    EndIf

    Local $pPropertyStore = $aRet[3]
    Local $pVTablePS = DllStructGetData(DllStructCreate("ptr", $pPropertyStore), 1)

    ; Create property key
    Local $tPropKey = _CreatePROPERTYKEY($PKEY_Title_FMTID, $PKEY_Title_PID)

    ; Create PROPVARIANT for title
    Local $tTitleWStr = DllStructCreate("wchar[" & StringLen($sTitle) + 1 & "]")
    DllStructSetData($tTitleWStr, 1, $sTitle)

    Local $tPropVar = DllStructCreate("word vt; word wReserved1; word wReserved2; word wReserved3; ptr pValue")
    DllStructSetData($tPropVar, "vt", 31) ; VT_LPWSTR
    DllStructSetData($tPropVar, "pValue", DllStructGetPtr($tTitleWStr))

    ; SetValue method (vtable index 5)
    Local $pSetValue = DllStructGetData(DllStructCreate("ptr", $pVTablePS + 5*4), 1)
    Local $aRet2 = DllCallAddress("long", $pSetValue, _
        "ptr", $pPropertyStore, _
        "ptr", DllStructGetPtr($tPropKey), _
        "ptr", DllStructGetPtr($tPropVar))

    ; Commit (vtable index 7)
    If Not @error And $aRet2[0] = 0 Then
        Local $pCommit = DllStructGetData(DllStructCreate("ptr", $pVTablePS + 7*4), 1)
        DllCallAddress("long", $pCommit, "ptr", $pPropertyStore)
    EndIf

    _ReleaseObject($pPropertyStore)
    Return True
EndFunc

Func _CreatePROPERTYKEY($sFmtID, $iPID)
    Local $tGUID = _GUIDFromString($sFmtID)
    Local $tPropKey = DllStructCreate("byte[16]; dword PID")

    ; Copy GUID to structure
    DllCall("kernel32.dll", "none", "RtlMoveMemory", _
        "ptr", DllStructGetPtr($tPropKey), _
        "ptr", DllStructGetPtr($tGUID), _
        "dword", 16)

    DllStructSetData($tPropKey, "PID", $iPID)
    Return $tPropKey
EndFunc

Func _GUIDFromString($sGUID)
    Local $tGUID = DllStructCreate("dword Data1; word Data2; word Data3; byte Data4[8]")
    DllCall("ole32.dll", "long", "CLSIDFromString", "wstr", $sGUID, "ptr", DllStructGetPtr($tGUID))
    Return $tGUID
EndFunc

Func _ReleaseObject($pObject)
    If $pObject Then
        Local $pVTable = DllStructGetData(DllStructCreate("ptr", $pObject), 1)
        If $pVTable Then
            ; Release is 3rd vtable entry (index 2)
            Local $pRelease = DllStructGetData(DllStructCreate("ptr", $pVTable + 2*4), 1)
            Local $aRet = DllCallAddress("dword", $pRelease, "ptr", $pObject)
            Return $aRet[0]
        EndIf
    EndIf
    Return 0
EndFunc

 

 

Screenshot 2026-01-02 200848.png

Posted (edited)

First the IID of ICustomDestinationList should be "{6332debf-87b5-4670-90c0-5e57b408a49e}".

Using ObjCreateInterface I was able to successfully create the object.  It should be easier to manage than with "CoCreateInstance".  And certainly more readable.

But if you insist going that path there is a number of problems with your first call, it should be something like this :

Local $tGUID1 = _GUIDFromString($CLSID_DestinationList)
    Local $tGUID2 = _GUIDFromString($IID_ICustomDestinationList)
    Local $aRet = DllCall("ole32.dll", "long", "CoCreateInstance", _
        "struct*", $tGUID1, _
        "ptr", 0, _
        "dword", 1, _ ; CLSCTX_INPROC_SERVER
        "struct*", $tGUID2, _
        "ptr*", 0)

I have a correctly returned pointer this way.

That should be enough to get you started.  But I strongly recommend that you go with ObjCreateInterface...

edit : where did you get that code ? Is this coming from some AI ?

Edited by Nine
Posted

Thank you,

Yes, I used AI as a base because I had no clue where to start and it was much more complicated than anticipated so figured I would post what I could here.

I really don't have a preference of how I get the end result, just can't wrap my head around it.

My fear is that using my code it could break (if it worked) if distributed killing the main function of my program. 

Posted

Auto hotkey have a v1 and a v2 of their versions that may give you a heads up ?

;~      https://github.com/gedoor/ahkLauncher/blob/main/lib/ICustomDestinationList.ahk
;~      https://github.com/Ixiko/AHK-libs-and-classes-collection/blob/master/libs/g-n/JumpList.ahk
21 minutes ago, FadeSoft said:

just can't wrap my head around it.

Neither can I. But I'll wait right here for working code :D  

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
9 hours ago, FadeSoft said:

I used AI as a base because I had no clue where to start and it was much more complicated than anticipated

Ok fair enough.  After taking a better look at the code, it stands like AI translated it from some other language to AutoIt.

The functionality seems alright although there are numerous errors in GUIDs, vtable numbers and DllCall parameters.  Not sure you will end up with a working script if you are to correct all the errors, as I have no experience with those particular interfaces.  I still believe that going with ObjCreateInterface would be a much more elegant solution, but at this stage, it would require quite a lot of work without knowing the outcome.

For now, if you want to take a shot at it, you should stay with "CoCreateInstance" and DllCall by address, correct the errors one after the other, until it is working as expected, then you could convert it to ObjCreateInterface.

Good luck.

Posted

yep @Nine is correct, this is all over the shop.

I started to fix a few things, and got down to here. But doesn't get any better.. There's about 3 or 4 issues in this one call.

; Get BeginList method (vtable index 3) ---Nope, it's 5. first is at $pVTable so offset = 4 * ptrSize
    Local $pBeginList = DllStructGetData(DllStructCreate("ptr", $pVTable + 4*4), 1) ; 4*4 only worx for x86

    ; Call BeginList
    Local $cMaxSlots = 0
    Local $pRemovedArray = 0
    Local $aRet2 = DllCallAddress("long", $pBeginList, _
        "ptr", $pDestList, _
        "uint*", 0, _ ; $cMaxSlots. This is an output param. should be 0 for DllCall. check $aRet[2] for Value
        "struct*", _WinAPI_GUIDFromString($IID_IObjectArray), _ ;GUIDFromString returns a struct. So use "struct*" not "ptr".
        "ptr*", 0)

This should help with the vtable order if you want to start on ObjCreateInterface...  FWIW the other GUIDs looked like they should be correct, based on some other dude's code on the interweb - so that's probably a good starting point too.

Maybe try wrapping a couple more funcs here and see how you get on - at least with understanding the process. Shout out if you run into trouble :)

#include <WinAPI.au3>

Global Const $CLSID_DestinationList = "{77f10cf0-3db5-4966-b520-b7c54fd35ed6}"
Global Const $IID_ICustomDestinationList = "{6332debf-87b5-4670-90c0-5e57b408a49e}"
Global Const $IID_IObjectArray = "{92ca9dcd-5622-4bba-a805-5e9f541bd8c9}"


Local $tagICustomDestinationList = "" & _
    "SetAppID hresult(wstr);"  & _
    "BeginList hresult(uint*; struct*; ptr*);"

;~ HRESULT SetAppID(
;~   [in] LPCWSTR pszAppID
;~ );

;~ HRESULT BeginList(
;~   [out] UINT   *pcMinSlots,
;~   [in]  REFIID riid,
;~   [out] void   **ppv
;~ );


Local $hResult, $cMinSlots, $pObjArr
$oDestList = ObjCreateInterface($CLSID_DestinationList, $IID_ICustomDestinationList, $tagICustomDestinationList)
$hResult = $oDestList.BeginList($cMinSlots, _WinAPI_GUIDFromString($IID_IObjectArray), $pObjArr)

ConsoleWrite("Slots: " & $cMinSlots & @CRLF)
ConsoleWrite("Object Arr: " & $pObjArr & @CRLF)
Posted (edited)
1 hour ago, MattyD said:
$tagICustomDestinationList
...
Local $tagICustomDestinationList = _
        "SetAppID hresult(wstr);" & _
        "BeginList hresult(uint*;clsid;ptr*);" & _
        "AppendCategory hresult(wstr;ptr);" & _
        "AppendKnownCategory hresult(int);" & _
        "AddUserTasks hresult(ptr);" & _
        "CommitList hresult();" & _
        "AbortList hresult();" & _
        "DeleteList hresult(wstr);" & _
        "GetRemovedDestinations hresult(clsid;ptr*);"
...

hope this is good. I hope it is correct close to it. 😅

Edited by argumentum
oops

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted

I appreciate everyone's help so far, I am definitely going to have to sit down and rethink this.

If AHK is capable of making it work, than Autoit should also be able to?

Maybe i'm missing something but Windows didn't do a great job at making this accessible for everyone or is it more a language limitation if not just a user error (me)

  • Solution
Posted (edited)

Here my latest attempt.  And again my curiosity got over me.

; From Nine : JumpList (tasks)
; https://www.autoitscript.com/forum/topic/213392-windows-11-jump-list-tasks/#findComment-1548652
#include <GUIConstants.au3>
#include <WinAPI.au3>

Opt("MustDeclareVars", True)

#Region Global Const
Global Const $CLSID_DestinationList = "{77f10cf0-3db5-4966-b520-b7c54fd35ed6}"
Global Const $IID_ICustomDestinationList = "{6332debf-87b5-4670-90c0-5e57b408a49e}"
Global Const $tag_ICustomDestinationList = _
    "SetAppID hresult(wstr);" & _
    "BeginList hresult(uint*;clsid;ptr*);" & _
    "AppendCategory hresult(wstr;ptr*);" & _
    "AppendKnownCategory hresult(int);" & _
    "AddUserTasks hresult(ptr);" & _
    "CommitList hresult();" & _
    "GetRemovedDestinations hresult(struct*;ptr*);" & _
    "DeleteList hresult(wstr);" & _
    "AbortList hresult();"

Global Const $IID_IObjectArray = "{92ca9dcd-5622-4bba-a805-5e9f541bd8c9}"
Global Const $IID_IPropertyStore = "{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}"
Global Const $tagIPropertyStore = _
    "GetCount hresult(dword*);" & _
    "GetAt    hresult(dword;ptr*);" & _
    "GetValue hresult(ptr;variant*);" & _
    "SetValue hresult(ptr;ptr);" & _
    "Commit   hresult();"
Global Const $FMTID_SummaryInformation = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"
Global Const $PIDSI_TITLE = 2
Global Const $VT_LPWSTR = 31

Global Const $CLSID_EnumerableObjectCollection = "{2d3468c1-36a7-43b6-ac24-d3f02fd9607a}"
Global Const $IID_IObjectCollection = "{5632b1a4-e38a-400a-928a-d4cd63230295}"
Global Const $tag_IObjectCollection = _
    "GetCount hresult(uint*);" & _
    "GetAt hresult(uint;struct*;ptr*);" & _
    "AddObject hresult(ptr);" & _
    "AddFromArray hresult(ptr*);" & _
    "RemoveObjectAt hresult(uint);" & _
    "Clear hresult();"

Global Const $CLSID_ShellLink = "{00021401-0000-0000-C000-000000000046}"
Global Const $IID_IShellLink = "{000214F9-0000-0000-C000-000000000046}"
Global Const $tag_IShellLinkW = _
    "GetPath hresult(wstr;int;ptr*;dword);" & _
    "GetIDList hresult(ptr*);" & _
    "SetIDList hresult(ptr*);" & _
    "GetDescription hresult(ptr;int);" & _
    "SetDescription hresult(wstr);" & _
    "GetWorkingDirectory hresult(otr;int);" & _
    "SetWorkingDirectory hresult(wstr);" & _
    "GetArguments hresult(ptr;int);" & _
    "SetArguments hresult(wstr);" & _
    "GetHotkey hresult(word*);" & _
    "SetHotkey hresult(word);" & _
    "GetShowCmd hresult(int*);" & _
    "SetShowCmd hresult(int);" & _
    "GetIconLocation hresult(wstr;int;int*);" & _
    "SetIconLocation hresult(wstr;int);" & _
    "SetRelativePath hresult(wstr;dword);" & _
    "Resolve hresult(hwnd;dword);" & _
    "SetPath hresult(wstr);"
#EndRegion

Example()

Func Example()
  Local $hGUI = GUICreate("Jump List Example", 400, 300)
  _WinAPI_SetCurrentProcessExplicitAppUserModelID("Jump List Example")

  GUICtrlCreateLabel("Right-click this application in the taskbar", 50, 120, 300, 30)
  GUICtrlSetFont(-1, 12)
  GUICtrlCreateLabel("Tasks: Notepad", 50, 150, 300, 30)
  GUISetState(@SW_SHOW)

  Local $oICustDestList = ObjCreateInterface($CLSID_DestinationList, $IID_ICustomDestinationList, $tag_ICustomDestinationList)
  ConsoleWrite("$oICustDestList : " & IsObj($oICustDestList) & @CRLF)
  Local $pList, $iMin = 1
  $oICustDestList.BeginList($iMin, $IID_IObjectArray, $pList)
  ConsoleWrite("$pList : " & $pList & @CRLF)

  Local $oIObjectCollection = ObjCreateInterface($CLSID_EnumerableObjectCollection, $IID_IObjectCollection, $tag_IObjectCollection)
  ConsoleWrite("$oIObjectCollection : " & IsObj($oIObjectCollection) & @CRLF)

  AddTaskToCollection($oIObjectCollection, "Notepad", @WindowsDir & "\notepad.exe", "", "Launch Notepad text editor", @SystemDir & "\imageres.dll", 19)
  Local $iCount
  $oIObjectCollection.GetCount($iCount)
  ConsoleWrite("returned " & $iCount & @CRLF)

  $oICustDestList.AddUserTasks($oIObjectCollection)
  $oICustDestList.CommitList()

  While True
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
    EndSwitch
  WEnd
EndFunc   ;==>Example

Func AddTaskToCollection(ByRef $oColl, $sTitle, $sPath, $sArg, $sDesc, $sIcon, $iIcon)
  Local $oShellLink = ObjCreateInterface($CLSID_ShellLink, $IID_IShellLink, $tag_IShellLinkW)
  ConsoleWrite("$oShellLink : " & IsObj($oShellLink) & @CRLF)
  $oShellLink.SetPath($sPath)
  If $sArg Then $oShellLink.SetArguments($sArg)
  $oShellLink.SetDescription($sDesc)
  $oShellLink.SetIconLocation($sIcon, $iIcon)

  Local $tText = DllStructCreate("wchar string[256]")
  $oShellLink.GetDescription(DllStructGetPtr($tText), 255)
  ConsoleWrite("Description : " & $tText.string & @CRLF)

  Local $iCount
  $oColl.GetCount($iCount)
  ConsoleWrite("before : " & $iCount & @CRLF)

  $oColl.AddObject($oShellLink)
  $oColl.GetCount($iCount)
  ConsoleWrite("after : " & $iCount & @CRLF)
  SetShellLinkTitle($oShellLink, $sTitle)
EndFunc   ;==>AddTaskToCollection

Func SetShellLinkTitle(ByRef $oShell, $sTitle)
  Local $pStore
  $oShell.QueryInterface($IID_IPropertyStore, $pStore)
  ConsoleWrite("$pStore : " & $pStore & @CRLF)
  Local $oIPropertyStore = ObjCreateInterface($pStore, $IID_IPropertyStore, $tagIPropertyStore)
  ConsoleWrite("$oIPropertyStore : " & IsObj($oIPropertyStore) & @CRLF)
  Local $tPropKey = _WinAPI_PKEYFromString($FMTID_SummaryInformation, $PIDSI_TITLE)

  ; Create PROPVARIANT for title
  Local $tTitleWStr = DllStructCreate("wchar string[" & StringLen($sTitle) + 1 & "]")
  $tTitleWStr.string = $sTitle

  Local $tPropVar = DllStructCreate("word vt; word wReserved1; word wReserved2; word wReserved3; ptr pValue")
  $tPropVar.vt = $VT_LPWSTR
  $tPropVar.pValue = DllStructGetPtr($tTitleWStr)
  $oIPropertyStore.SetValue(DllStructGetPtr($tPropKey), DllStructGetPtr($tPropVar))

  Local $sText
  $oIPropertyStore.GetValue(DllStructGetPtr($tPropKey), $sText)
  ConsoleWrite("Title : " & $sText & @CRLF)
EndFunc   ;==>SetShellLinkTitle

Func _WinAPI_PKEYFromString($sPKEY, $iPID)
  Local $tPKey = DllStructCreate("byte GUID[16]; dword PID;")
  DllCall("propsys.dll", "long", "PSPropertyKeyFromString", "wstr", $sPKEY, "struct*", $tPKey)
  $tPKey.PID = $iPID
  Return $tPKey
EndFunc   ;==>_WinAPI_PKEYFromString

 

Code updated.  Should now work as expected.

ps.  Just noticed that some of the tags I made did not end each method with ;
       Funny that it is working without it.  Anyway added it.

Edited by Nine
Posted

Incredible job! 😁

Excellent base to learn and work from, you should consider sharing it in the example forums! As I don't want to take credit for your work but I don't think this has been done before at least anything I could find related to Autoit.

This would add a lot to already existing Autoit programs for sure!

Thanks again 🙂

Posted

I was having a look at the example, and got a bit sidetracked with a thought.. (apologies if its a bit too far off topic)

Take the line: $oIObjectCollection.QueryInterface($IID_IObjectArray, $pArray).

A successful QueryInterface performs an addRef on the object, so we now have 2 references... One for $oIObjectCollection on IObjectCollection, and another for $pArray on IObjectArray.

Now when an autoit variable of type "object" goes out of scope, I believe Autoit will release a reference to that object. But, does it only call IUnknown::Release() once at that point?? I would think that'd be logical because we could have references to it elsewhere... My understanding is that once the refcount reaches 0, the system automatically frees the obj - this is not something AutoIt explicitly does. It's why there's no reciprocal cleanup func for the cocreateinstance system call...

Assuming all that is true, then I guess when Example() exits we probably get stuck with a rogue object in memory...That's because AutoIt thinks $pArray just a random ptr. So when that's invalidated, its reference is never released. Well that's the theory.  

If the scenario is correct, then we should really do a $oIObjectCollection.Release() once at some point after calling QI.

I'll have a play at some point tomorrow time permitting!

Posted
11 minutes ago, MattyD said:

then we should really do a $oIObjectCollection.Release()

While True
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd
    $oIObjectCollection.GetCount($iCount)
    ConsoleWrite("returned " & $iCount & @CRLF)
    $oIObjectCollection.Release()
    $oIObjectCollection.GetCount($iCount)
    ConsoleWrite("returned " & $iCount & @CRLF)

I don't think that does it.

11 minutes ago, MattyD said:

I'll have a play at some point tomorrow time permitting!

:)

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted
#include <MsgBoxConstants.au3>

If $CmdLine[0] > 0 Then
    Switch $CmdLine[1]
        Case "/func1"
            Function1()
            Exit
        Case "/func2"
            Function2()
            Exit
        Case "/add", "-a"
            If $CmdLine[0] < 3 Then
                MsgBox($MB_OK, "Error", "/add requires two numbers" & @CRLF & "Usage: " & @ScriptName & " /add <num1> <num2>")
                Exit
            EndIf
            AddNumbers($CmdLine[2], $CmdLine[3])
            Exit
        Case "/help", "-h", "-?"
            ShowHelp()
            Exit
    EndSwitch
EndIf

Func Function1()
    MsgBox($MB_OK, "Function1", "Function1 executed!")
EndFunc

Func Function2()
    MsgBox($MB_OK, "Function2", "Function2 executed!")
EndFunc

Func AddNumbers($num1, $num2)
    If Not StringIsDigit($num1) Or Not StringIsDigit($num2) Then
        MsgBox($MB_OK, "Error", "Both parameters must be numbers!")
        Exit
    EndIf

    Local $result = Number($num1) + Number($num2)
    MsgBox($MB_OK, "Adding", $num1 & " + " & $num2 & " = " & $result)
EndFunc

Func ShowHelp()
    MsgBox($MB_OK, "Help", "/func1" & @CRLF & "/func2" & @CRLF & "/help /-h /-?")
EndFunc

Can call commands from the task bar

Posted

@MattyD  Yes I thought of it and did some tests.  At this stage I have no reason to believe that there is memory leak after the application is closed.  Doing repeating run/close does not increment memory usage nor increase handles number.  My thought was that C++ garbage collector (AutoIt3.exe) is taking care of it.  But I could be wrong on this one.  However, if there is memory leak, it is very very minimal.  Not observable from task manager...

Posted
2 hours ago, argumentum said:
While True
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd
    $oIObjectCollection.GetCount($iCount)
    ConsoleWrite("returned " & $iCount & @CRLF)
    $oIObjectCollection.Release()
    $oIObjectCollection.GetCount($iCount)
    ConsoleWrite("returned " & $iCount & @CRLF)

I don't think that does it.

:)

GetCount is a count on the objects in the collection, which is a different thing to the refcount used in IUnknown ;),

These funcs will allow us to interact with the object via the $pArray param.

Func _AddRef($pThis)
    Local $pVTab = DllStructGetData(DllStructCreate("ptr", $pThis), 1)
    Local $pFunc = DllStructGetData(DllStructCreate("ptr", $pVTab + (@AutoItX64 ? 8 : 4)), 1)
    Local $aCall = DllCallAddress("uint", $pFunc, "ptr", $pThis)
    Return $aCall[0]
EndFunc

Func _Release($pThis)
    Local $pVTab = DllStructGetData(DllStructCreate("ptr", $pThis), 1)
    Local $pFunc = DllStructGetData(DllStructCreate("ptr", $pVTab + 2 * (@AutoItX64 ? 8 : 4)), 1)
    Local $aCall = DllCallAddress("uint", $pFunc, "ptr", $pThis)
    Return $aCall[0]
EndFunc

And if you "Return String($pArray)" from Example()...
I'm using String() to ensure $pArray as a variable is totally invalidated - so it isn't somehow preserved/copied etc, by being Returned.

Local $pObj Ptr(Example())
ConsoleWrite(_AddRef($pObj) & @CRLF)
ConsoleWrite(_AddRef($pObj) & @CRLF)
ConsoleWrite(_AddRef($pObj) & @CRLF)

And you get:

2
3
4

So yep - it seems the object sticks around and has a refcount of 1 after Example() exits.

Posted
Spoiler
AI Overview
 
 
 
In .NET applications (specifically WPF using the
System.Windows.Shell namespace), the JumpList object does not need to be explicitly released or disposed of with a using statement or similar mechanism in C# or Visual Basic. 
 
The JumpList class manages the items in a Windows application's Jump List, which is a feature of the Windows Shell. 
 
 
Key Points on the JumpList Object Lifecycle
 
  • Automatic Management: When you create a JumpList and associate it with the current Application object (either in XAML or by calling the static JumpList.SetJumpList method), the Windows Shell takes ownership of displaying the items.
  • Shell Handles Persistence: The operating system handles the storage and persistence of the actual Jump List data in specific system directories (%APPDATA% related folders). Your application provides the instructions for what should be in the list, but Windows manages the data file and its lifecycle on the system.
  • No Unmanaged Resources: The JumpList class itself is a managed .NET object and does not hold significant unmanaged resources that require manual cleanup. The .NET garbage collector will reclaim the memory when the object is no longer referenced.
  • Updating the List: To modify or clear the list, you interact with the JumpItems collection and then call the Apply method, or call SetJumpList with a new object. This sends the updated instructions to the Windows Shell, which then updates the persistent data. 
  •  
In summary, the developer does not need to worry about manually "releasing" the JumpList object itself; the system manages the underlying data structure and file persistence. 

AI say that it does not need releasing/clean up, because the shell/OS takes care of it.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted

I haven't tested the code, so this is just a guess
The reference count increases because of $oIObjectCollection.QueryInterface($IID_IObjectArray, $pArray)

However, is that line necessary? I believe BeginList already returns an IObjectArray($pList) 
$oICustDestList.BeginList($iMin, $IID_IObjectArray, $pList)

Posted
32 minutes ago, jugador said:

However, is that line necessary? I believe BeginList already returns an IObjectArray($pList) 

Yes and no.  Using $pList does not work, it must come from $oIObjectCollection.  However, you made me think to use the object itself instead of a IObjectArray *poa as described in MSDN.  And it works.  Thanks you for that.  Code updated.

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
×
×
  • Create New...