;------------------------------------------------------------------------------------------
;
;   FileOpsDelete.asm - Jeremy Gordon
;	February 2025
;
;   This demonstrates using the FileOperations COM interface through the CoCreateInstance API
;	and also using the ShellItem interface
;	This is easy to do in assembler, in fact easier and more obvious than in "C"
;
;	This is written in GoAsm, one of the GoTools available from www.GoDevTool.com
;
;	Assemble using GoAsm FileOpsDelete.asm (produces PE COFF file)
;	Then link using:-
;   GoLink FileOpsDelete.obj user32.dll kernel32.dll ole32.dll Shell32.dll -files -debug coff
;
;	As written, the file C:/temp/FileOpsDelete.txt is deleted
;   
;------------------------------------------------------------------------------------------
;
;Here is a structure declaration containing the vtable for the FileOperation interface.
;At compile time this gives the offset into the vtable of the particular function being called
;this comes from ShObjidl_core.h
IFileOperation STRUCT
		QueryInterface 		DD 0 ;+0h (This,riid,ppvObject)
		AddRef 				DD 0 ;+4h (This)
		Release 			DD 0 ;+8h (This)
		Advise 				DD 0 ;+0Ch (This,pfops,pdwCookie)
		Unadvise 			DD 0 ;+10h (This,dwCookie)
		SetOperationFlags 	DD 0 ;+14h (This,dwOperationFlags)
		SetProgressMessage 	DD 0 ;+18h (This,pszMessage)
		SetProgressDialog 	DD 0 ;+1Ch (This,popd)
		SetProperties 		DD 0 ;+20h (This,pproparray)
		SetOwnerWindow 		DD 0 ;+24h (This,hwndOwner)
		ApplyPropertiesToItem 	DD 0 ;+28h (This,psiItem)
		ApplyPropertiesToItems 	DD 0 ;+2Ch (This,punkItems)
		RenameItem 			DD 0 ;+30h (This,psiItem,pszNewName,pfopsItem)
		RenameItems 		DD 0 ;+34h (This,pUnkItems,pszNewName)
		MoveItem 			DD 0 ;+38h (This,psiItem,psiDestinationFolder,pszNewName,pfopsItem)
		MoveItems 			DD 0 ;+3Ch (This,punkItems,psiDestinationFolder)
		CopyItem			DD 0 ;+40h (This,psiItem,psiDestinationFolder,pszCopyName,pfopsItem)
		CopyItems 			DD 0 ;+44h (This,punkItems,psiDestinationFolder)
		DeleteItem 			DD 0 ;+48h (This,psiItem,pfopsItem)
		DeleteItems 		DD 0 ;+4Ch (This,punkItems)
		NewItem 			DD 0 ;+50h (This,psiDestinationFolder,dwFileAttributes,pszName,pszTemplateName,pfopsItem)
		PerformOperations 	DD 0 ;+54h (This)
		GetAnyOperationsAborted DD 0 ;+58h (This,pfAnyOperationsAborted)
IFileOperation ends
;
;Here is the vtable for the IShellItem interface which is also used here
;this comes from ShObjidl_core.h
IShellItem STRUCT
		QueryInterface 		DD 0 ;+0h (This,riid,ppvObject)
		AddRef 				DD 0 ;+4h (This)
		Release 			DD 0 ;+8h (This)
		BindToHandler		DD 0 ;+0Ch (This,pbc,bhid,riid,ppv)
		GetParent			DD 0 ;+10h (This,ppsi)
		GetDisplayName		DD 0 ;+14h (This,sigdnName,ppszName)
		GetAttributes		DD 0 ;+18h (This,sfgaoMask,psfgaoAttribs)
		Compare				DD 0 ;+1Ch (This,psi,hint,piOrder)
IShellItem ends
;
;------------------------------------------------------------------------
;
; The CoInvoke MACRO makes it easier to use the interfaces
; This makes use of GoAsm's ARGCOUNT
; ARGCOUNT is an assemble-time counter returning the number
; of arguments known to GoAsm when INVOKE is used
; See example below
;
;------------------------------------------------------------------------
;
CoInvoke(%o,%p,%a,%b,%c,%d) MACRO
    #if ARGCOUNT == 6
    PUSH %d
    #endif
    #if ARGCOUNT >= 5
    PUSH %c
    #endif
    #if ARGCOUNT >= 4
    PUSH %b
    #endif
    #if ARGCOUNT >= 3
    PUSH %a
    #endif
    mov eax,[%o]
    push eax
    mov eax,[eax]
    call [eax+%p]
ENDM
;
;for example CoInvoke(iFILEOPERATION,IFileOperation.SetOperationFlags,20080414h)
;resolves to push 20080414h, mov eax,[iFILEOPERATION], push eax, mov eax,[eax], call [eax+14h]
;
GUID STRUCT
    DD 0
    DW 0
    DW 0
    DB 0
    DB 7 DUP 0 
ENDS
;
DATA SECTION
;
iFILEOPERATION		DD 0
SHELLITEM			DD 0
;
;the COM class is identified by a unique number (a CLSID)
;the COM interface is also identified by a unique number (an IID)
;these numbers are declared in ShObjidl_core.h as follows
;CLSID_FileOperation = '{3AD05575-8857-4850-9277-11B85BDB8E09}'
;IID_IFileOperation  = '{947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8}'
;these are GUID type numbers and are a dword, word, word, word and 8 bytes
;Since Windows writes such numbers in memory/data in little-endian format it writes each 
;component backwards (least significant byte first).  Windows breaks with convention by
;regarding the last word as two bytes (which are therefore written in sequence).  This is
;all dealt with as follows together with the GUID structure above
CLSID_FileOperation GUID <3AD05575h,8857h,4850h,92h,<77h,11h,0B8h,5Bh,0DBh,8Eh,9h>>
IID_IFileOperation  GUID <947AAB5Fh,0A5Ch,4C13h,0B4h,<0D6h,4Bh,0F7h,83h,6Fh,0C9h,0F8h>>
;IID_IShellItem is declared as 43826d1e-e718-42ee-bc55-a1e261c37bfe, so
IID_IShellItem		GUID <43826D1Eh,0E718h,42EEh,0BCh,<55h,0A1h,0E2h,61h,0C3h,7Bh,0FEh>>
;
MESSAGE_BUFFER DB 256 DUP 0
;
CODE SECTION
;
START:
INVOKE CoInitializeEx,0,0			;0=COINIT_MULTITHREADED
INVOKE CoCreateInstance,ADDR CLSID_FileOperation,0,17h,ADDR IID_IFileOperation,ADDR iFILEOPERATION 		
;17h=CLSCTX_ALL (finds most convenient)
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
;FOF_SILENT=4h, FOF_NOCONFIRMATION=10h, FOF_ALLOWUNDO=40h or preferably FOFX_ADDUNDORECORD=20000000h
;FOF_NOERRORUI=400h, FOFX_RECYCLEONDELETE=80000h
CoInvoke(iFILEOPERATION,IFileOperation.SetOperationFlags,20080414h)
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
MOV ESI,ADDR L'C:\temp\FileOpsDelete.txt'		;must use utf16, backslash and start with drive
INVOKE SHCreateItemFromParsingName,ESI,0,ADDR IID_IShellItem,ADDR SHELLITEM
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
CoInvoke(iFILEOPERATION,IFileOperation.DeleteItem,[SHELLITEM],0)	
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
CoInvoke(iFILEOPERATION,IFileOperation.PerformOperations)	;carry out all previous instructions
OR EAX,EAX				;see if S_OK
JNZ >L2					;no, exit
INVOKE MessageBoxA,0,ADDR 'File deleted',ADDR 'FileOpsDelete',0      	;ok button only
JMP >.fin
L2:
MOV EDI,ADDR MESSAGE_BUFFER
PUSH 0,256D,EDI			;256=size of buffer
PUSH 0,EAX,0			;eax=error result
PUSH 1000h              ;look at system message table
CALL FormatMessageA     ;format message from system into buffer
INVOKE MessageBoxA,0,EDI,ADDR 'error',0      	;ok button only
.fin:
MOV ECX,[SHELLITEM]
JECXZ >L3
CoInvoke(SHELLITEM,IShellItem.Release)
L3:
MOV ECX,[iFILEOPERATION]
JECXZ >L4
CoInvoke(iFILEOPERATION,IFileOperation.Release)
L4:
INVOKE CoUninitialize
INVOKE ExitProcess,0
