Link to home
Start Free TrialLog in
Avatar of Luis Diaz
Luis DiazFlag for Colombia

asked on

AutoHotkey: save as pdf

Hello experts,

I am trying to set up an AutoHotkey script that I will use on a regular basis to swiftly export file as Pdf and open folder in which it has been saved


I need some advice with the following:
1.      Coding structure and optimization proposal.
2.      Line 6 to 8 are required?
3.      Set up a return if active document has not been saved.
Example: I open a document and I forgot to save the document. If I launch the script an if statement should be set up to block the pdf export.
4.      Find the right saveas method for powerpoint and excel the save as method applied doesn't work. File is exported however When I open the file I got an error message.
5.      End the script by opening the pdf export file
6.      Find a method to applied the same mechanism for outlook>emails
If some requirements are complicated to cover I am willing to give up
If you have questions, please contact me.
SaveAsPdfSwitchCase.txt
Avatar of Joe Winograd
Joe Winograd
Flag of United States of America image

Hi Luis,

> 1. Coding structure and optimization proposal.

Your switch-case code matches the structure in my EE AHK Switch-Case article and is a good choice for this.

Why do you have three separate MsgBox statements in each path? One would be sufficient.

Why are you using the clipboard? I don't see any need for it, unless you have a reason for wanting the file name on the clipboard after the hotkey fires.

> 2. Line 6 to 8 are required?

Yes.

> 3. Set up a return if active document has not been saved.

I don't know how to determine if the active file has been saved. One idea is to save the file in its own format before doing the save to PDF.

> 4. Find the right saveas method for powerpoint and excel the save as method applied doesn't work.

The SaveAs method also works for Excel and PowerPoint, but the enumeration values are not 17 — for Excel, it is 57; for PowerPoint, it is 32.

> 5. End the script by opening the pdf export file

This will open the file in whatever program owns the PDF file type on your system:

Run,%FullNamePDF%

Open in new window

Of course, you need to get the correct value into FullNamePDF. I suggest creating a variable with the PDF file name via a separate assignment statement rather than burying it inside the ComObjActive call.

> 6. Find a method to applied the same mechanism for outlook>emails

I've never done this and don't know off the top of my head what method or enumeration value is required. You'll need to research it. Note that the SaveAs method for Outlook does not support PDF:
https://docs.microsoft.com/en-us/office/vba/api/outlook.olsaveastype

Of course, you could do a SaveAs to Word first (olDoc=4) and then SaveAs that to a PDF, but if you research it further, there may be a direct way (perhaps with an Export method instead of the SaveAs method).

Btw, I noticed this line in your code:

ComObjActive("Word.Application").ActiveDocument.SaveAs2(FullNameVarNoExt . "_" . CurrentDateTime . ".pdf", 17)

Open in new window

Note that you do not have the path in the name of the saved PDF. I suggest creating a Path variable in the SplitPath command (third param...OutDir) and using that in the statement above. Or, even better, create a variable with the full name of the PDF file, as I mentioned earlier, and use that in the line above. Regards, Joe
Avatar of Luis Diaz

ASKER

Hi Joe,

Here is my comment:

1>You are right I removed clipboard. New version include this.
3>"One idea is to save the file in its own format before doing the save to PDF." We are in the same page.
I was thinking to define a default folder for files which haven't been saved on its own format such as :
EnvGet,FolderPath,UserProfile
%FolderPath%\Downloads

Open in new window

and save file prior to perform the pdf export.
Could you please help me to adapt? The idea will be to force the save of the file if it doesn't exist at download folder and then export pdf file.
For files which already exist, pdf should be exported at the same folder of  initial (doc,xls,ppt..) file.
4>The SaveAs method also works for Excel and PowerPoint, but the enumeration values are not 17 — for Excel, it is 57; for PowerPoint, it is 32.
New Version include this.
5> "This will open the file in whatever program owns the PDF file type on your system:"
I am trying to review this however I have some issues as I am not able to properly get the OutputDir (PdfFileFolder) and as a result PDF file full name. If you can help me with this it would be great (as of line 46)
6>I am going to continue the research however for the moment I prefer to have an stable version for excel, powerpoint and word.

Thank you in advance for your help.

Regards,
Luis.
SaveAsPdfSwitchCase2.ahk
Hi Luis,

> I was thinking to define a default folder for files which haven't been saved on its own format such as

I was thinking of simply saving the file, like this:

ComObjActive("Excel.Application").ActiveWorkbook.Save()
ComObjActive("PowerPoint.Application").ActivePresentation.Save()
ComObjActive("Word.Application").ActiveDocument.Save()

Open in new window

If it has already been saved, it doesn't hurt; if it hasn't been saved, this saves it.

> I am trying to review this however I have some issues as I am not able to properly get the OutputDir (PdfFileFolder)

Your PdfFileVar assignment statements on lines 18, 27, and 36 all use just the file name with no path, as I mentioned in my previous post. Fix that.

> I am going to continue the research however for the moment I prefer to have an stable version for excel, powerpoint and word.

OK, we'll get it working just for Excel, PowerPoint, and Word.

Regards, Joe
Hi Joe,

I tried to fix it as as much as I can but I have some issues with the AutoHotkey script.
When I run the the process from a file which is at download folder it works however from another folder it doesn't I don't understand what is wrong.

If you can help me with this it would be great!

Thank you for your help.
SaveAsPdfSwitchCase3.ahk
When I run the the process from a file which is at download folder it works however from another folder it doesn't I don't understand what is wrong.
I've already told you three times what is wrong:

(1) "Of course, you need to get the correct value into FullNamePDF. I suggest creating a variable with the PDF file name via a separate assignment statement rather than burying it inside the ComObjActive call."

(2) "Note that you do not have the path in the name of the saved PDF. I suggest creating a Path variable in the SplitPath command (third param...OutDir) and using that in the statement above. Or, even better, create a variable with the full name of the PDF file, as I mentioned earlier, and use that in the line above."

(3) "Your PdfFileVar assignment statements on lines 18, 27, and 36 all use just the file name with no path, as I mentioned in my previous post. Fix that."

As you know, I'm trying to get you self-sufficient on writing AutoHotkey scripts. At this point on this particular one, I'll do it for you. It is attached. Tested here on W10 with Excel 365, PowerPoint 365, and Word 365...all three worked perfectly.

Btw, please read my EE article again on AHK's Switch-Case:
AutoHotkey Switch-Case

Pay particular attention to the Default: path in the article and compare it to your line 43...you need to see why your Case Default: statement is wrong.

Also, look at your MsgBox on line 44. Do you see what's wrong? The commas must be escaped, otherwise they will be treated as separating parameters rather than as literal text for the message.

Regards, Joe
SaveAsPdfSwitchCase3JW.ahk
Hi Joe,

I've already told you three times what is wrong:

I thought that PdfVar should be reported outside the switch cases as I did like this:
PdfFullFileName:=OutputDir . "\" . PdfFileVar 

Open in new window

My mistake it should be within the switch cases as you did.

Your code proposal looks excellent, the following part helps me a lot to understand and to simplify the sequence:
oExcel:=ComObjActive("Excel.Application")
oPowerPoint:=ComObjActive("PowerPoint.Application")
oExcel:=ComObjActive("Excel.Application")
oWord:=ComObjActive("Word.Application")

Open in new window


This was the first think that I should consider prior to perform the switch case. Define the ComObjActive variables. By doing so I could properly manage the various sequence such as the save action and splitpath.

Pay particular attention to the Default: path in the article and compare it to your line 43...you need to see why your Case Default: statement is wrong.

I reviewed my proposal, compared to the switch case reported in your article. In fact I putted a Case prior to Default, my mistake.

Also, look at your MsgBox on line 44. Do you see what's wrong? The commas must be escaped, otherwise they will be treated as separating parameters rather than as literal text for the message.

Indeed, sorry for that.

I am going to assign your solution and in the meantime see if I can found a way to manage outlook case. I hope this is fine with you.
Additionally, I take the opportunity to ask you this question.
Given the fact that I was trying to debug the code, I was asking myself how do you set up breakpoint in AutoHotkey.
Example: Instead of doing the whole sequence I would like to set up a breakpoint to just perform till a particular line. When I tried to set up the pdf export AutoHotkey, I was asking my self how to avoid exporting and just getting the Msgbox and test the switch cases. Of course I can comment the lines that I don't want to be executed however do you know if there is another way in AutoHotkey to breakpoint as we have in excel VBA?

Thank you for your help.
> My mistake it should be within the switch cases as you did.

Yes, because getting the full file name depends on which Office app it is.

> the following part helps me a lot to understand and to simplify the sequence

I'm glad that helps you to understand.

> In fact I putted a Case prior to Default, my mistake.

Glad you see that.

> Indeed, sorry for that.

Glad you see that, too.

> I am going to assign your solution and in the meantime see if I can found a way to manage outlook case.

I don't know how to go directly to PDF from Outlook off the top of my head (like you, I'd have to research it). But, as I mentioned earlier, you could do a SaveAs to a temp Word file first (olDoc=4) and then SaveAs that temp Word file to a PDF. I did that...it is in the attached revision (there could be...should be...more error checking, but I'll leave that up to you). Tested on W10 with Outlook 365...worked perfectly...on both an opened message and a selected (but not opened) message.

> Given the fact that I was trying to debug the code, I was asking myself how do you set up breakpoint in AutoHotkey.

I don't do that. I have found that I need nothing more than MsgBox (or sometimes FileAppend) statements to debug my code. But I know that you use SciTE4AutoHotkey, which has debugging support, including breakpoints. If you're not happy with that in SciTE4AutoHotkey, another well-regarded AutoHotkey IDE with debugging support, including breakpoints, is AHK Studio:
http://www.maestrith.com/ahk-studio/

> Of course I can comment the lines that I don't want to be executed however do you know if there is another way in AutoHotkey to breakpoint as we have in excel VBA?

I often do an immediate Return or ExitApp instead of (or in addition to) commenting out code. But you may be happier with an IDE (I prefer a powerful text editor rather than an IDE...but that's me). Regards, Joe
SaveAsPdfSwitchCase4JW.ahk
Hi Joe,

>Given the fact that I was trying to debug the code, I was asking myself how do you set up breakpoint in AutoHotkey.
I am going to take the simplest solution and do and Immediate Return or an ExitApp. Great advice!
> I did that...it is in the attached revision
Thank you very much for this Joe! This helps me a lot. I tested in both of my computers with an Excel, Word, PowerPoint and Outlook (workbook, document, presentation and e-mail) and it works!
I was wondering if we can perform the following reviews:
1.Instead of generating PDF file issued by outlook at Temp folder, generate the file at Download folder.
I tried to include this in the attached version. I also test and it works here. However I would like to have your advice.
2.Open containing folder in which has been generated PDF file. OutDir (Folder variable of PDF file is already in the switch case) however this is not the case for outlook, I was wondering what is the best way to do this.

I attached the ongoing version. This is the last revision, I hope this is ok with you.
Thank you again for your help.
SaveAsPdfSwitchCase5LD.ahk
> I tested in both of my computers with an Excel, Word, PowerPoint and Outlook (workbook, document, presentation and e-mail) and it works!

Great news!

> Instead of generating PDF file issued by outlook at Temp folder, generate the file at Download folder.

Sure.

> I tried to include this in the attached version. I also test and it works here. However I would like to have your advice.

Your code looks good.

> Open containing folder in which has been generated PDF file. OutDir (Folder variable of PDF file is already in the switch case) however this is not the case for outlook, I was wondering what is the best way to do this.

All of the other Case blocks put the containing folder in a variable called Folder. So, simply do that in the Outlook Case block, i.e., instead of calling the variable DownloadFolder, call it Folder. In other words, change these lines:

  DownloadFolder:=FolderPath . "\Downloads"
  WordTempFile:=DownloadFolder . "\Email_" . CurrentDateTime . ".doc"
  PdfFile:=DownloadFolder . "\Email_" . CurrentDateTime . ".pdf"

Open in new window


To these lines:

  Folder:=FolderPath . "\Downloads"
  WordTempFile:=Folder . "\Email_" . CurrentDateTime . ".doc"
  PdfFile:=Folder . "\Email_" . CurrentDateTime . ".pdf"

Open in new window

Then change line 77 (;~ Run,explorer.exe) to this:

Run,explorer.exe "%Folder%"

Open in new window

That will now work for all four apps. Regards, Joe
Hi Joe,
I reviewed as recommended. Please find attached the revised version. I made the test with the 4 apps and it works here.
Prior to add to my AutoHotkey/Hotstring I was wondering if you see an issue. If so, please let me know.
Question:
I noticed the following at line 43 : 4==>DOC, I suppose this is a comment but semicolon is missing, can I comment it?

Thank you for your help.
SaveAsPdfSwitchCase6LD.ahk
> I made the test with the 4 apps and it works here.

Glad to hear it!

> Prior to add to my AutoHotkey/Hotstring I was wondering if you see an issue.

No issues...looks fine!

> I suppose this is a comment but semicolon is missing, can I comment it?

Good catch! Yes, it's a comment and there should be a semi-colon:

  oItem.SaveAs(WordTempFile,4) ; 4==>DOC

Open in new window

Regards, Joe
Thank you very much Joe.
As soon as I add this to my AutoHotkey/Hotstring I will mark the question as solved. Thank you again for your help!
ASKER CERTIFIED SOLUTION
Avatar of Joe Winograd
Joe Winograd
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Joe,

Thank you very much for this useful comment.
Something that I don't understand is why the documentation reports the following: "This value should be used only for debugging purposes."
Additionally, There are some AutoHotkey scripts reported in my AutoHotkey/Hotstring file which used:
xl:=ComObjActive("Excel.Application")

Open in new window

The advice will be to finish them with:
ObjRelease(xl)

Open in new window


Thank you in advance for your help.

Regards,
Luis.
> "This value should be used only for debugging purposes."

I don't know why it says that, but I've never used the return value, anyway (which is the new reference count). There's some related discussion in these two sections, but it's certainly not clear:

Reference-Counting
Pointers to Objects

> The advice will be to finish them with:
> ObjRelease(xl)

Yes, that's what I do. When I'm done using a COM Object, I release it via ObjRelease. Regards, Joe
Thank you for your comment Joe.

As regards COM Object, I have the following AutoHotkey scripts used on a daily basis,

;====================================
;Copy full path
;====================================

+F8::
WinGet ActiveWindowPID,PID,A ; save active window PID
WinActivate % "ahk_id " DllCall("GetDesktopWindow","ptr") ; register Office app in ROT - thanks to lexikos for this line of code
WinActivate,ahk_pid %ActiveWindowPID% ; after registering Office app in ROT, reactivate Office app
If (WinActive("ahk_exe excel.exe"))
  Clipboard:=ComObjActive("Excel.Application").ActiveWorkbook.FullName
Else
If (WinActive("ahk_exe powerpnt.exe"))
  Clipboard:=ComObjActive("PowerPoint.Application").ActivePresentation.FullName
Else
If (WinActive("ahk_exe winword.exe"))
  Clipboard:=ComObjActive("Word.Application").ActiveDocument.FullName
Else
If (WinActive("ahk_exe notepad++.exe"))
 Send,^+p
Else
If (WinActive("ahk_exe scite.exe"))
{
  Send,{Alt}
  Sleep, 100
  Send,f
  Sleep,100
  Send,h
}
Else
{
  MsgBox,4144,Error,Active window is not Excel, PowerPoint, Word, Notepad++ or SciTE4AutoHotkey
  Return
}
ClipWait,1 ; wait for file name to appear on clipboard
If (ErrorLevel=1)
{
  MsgBox,4144,Error,File name did not appear on clipboard after one second
  Return
}
FullName:=Clipboard ; get full file name from clipboard
Return

Open in new window


The best practice is to put ObjRelease prior to return?

Same question for:

;====================================
;Excel Attributes
;====================================
#IfWinActive,ahk_exe excel.exe ; context-sensitive for Excel
+^F11::
XL:=ComObjActive("Excel.Application")
FirstRow:=XL_First_Row(XL)
LastRow:=XL_Last_Row(XL)
UsedRange:=XL_Used_Range(XL)

ExcelAttributes:="FirstRow: " FirstRow . A_Space . "LastRow: " LastRow . A_Space . "UsedRange: "  UsedRange
Clipboard:=ExcelAttributes
ClipWait,1 ; wait for info to appear on clipboard
If (ErrorLevel=1)
{  
  MsgBox,4144,Error,Information did not appear on clipboard after one second
  Return
}
MsgBox,4160,Success, %ExcelAttributes% have been copied
Return
#IfWinActive ; turn off context-sensitivity

Open in new window


Thank you for your help.
> The best practice is to put ObjRelease prior to return?

Release the object when you're done using it...that could be right before a Return or well prior to returning....whenever you're done using the object.

> Same question for:

Same answer.
Hi Luis,

To help you think through this, attached are rewrites of the two scripts that you posted above. You can see that I release the objects after their last usage, which occurs well before the Return.

Note that a common practice in AHK coding is to prefix with a letter "o" (lower case) the name of a variable that is used to contain an object, such as:

oExcel
oOutlook
oPowerPoint
oWord

Some folks go further and use "obj" or "Obj", such as:

objExcel
objOutlook
objPowerPoint
objWord

ObjExcel
ObjOutlook
ObjPowerPoint
ObjWord

It doesn't matter to the AHK interpreter...it is simply a coding practice/style that helps you to write code that is easy to understand and maintain.

Another related practice/style is the naming convention for the other part of the variable names. The examples above use long variable names...some folks prefer short ones, such as:

oXL
oOL
oPP
oWD

objXL
objOL
objPP
objWD

ObjXL
ObjOL
ObjPP
ObjWD

You should pick a naming convention that you like and use it consistently. Regards, Joe
CopyFullPath.ahk
ExcelAttributes.ahk
Hi Joe,

Thank you very much for your last comment. I will not mark the question as solved yet in order to remember that I need to add this to my AutoHotkey/Hotstring file and also review and test the other AutoHotkey scripts which use ComObjActive.

I hope this is fine with you.

Regards,
Luis.
Hi Luis,
Fine with me! Regards, Joe
I will not mark the question as solved yet in order to remember that I need to add this to my AutoHotkey/Hotstring file and also review and test the other AutoHotkey scripts which us ComObjActive.
Hi Luis,
Where do things stand on this? Do you need any additional help? Regards, Joe
Hi Joe,

I planned to do the update this weekend.

Regards,
Luis.
That's fine, Luis, no rush. I just wanted to be sure that you don't need more help with it. Regards, Joe
Hi Joe,

The following functions which are part of my AutoHotkey/Hotstring use ComObj, should I also release them as for the other scripts?

;====================================
;Function to get attributes of file selected
;AutoHotkey: Edit file based on extension
;====================================

GetSelectedFile(hwnd:="",FileComponents:=1,GetDate:=1) {
; this function by sinkfaze: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=77#p395
  WinGet, process, processName, % "ahk_id" hwnd := hwnd? hwnd:WinExist("A")
  WinGetClass class, ahk_id %hwnd%
  if  (process = "explorer.exe")
    if  (class ~= "Progman|WorkerW") {
      ControlGet, files, List, Selected Col1, SysListView321, ahk_class %class%
      Loop, Parse, files, `n, `r
        ToReturn .= A_Desktop "\" A_LoopField "`n"
    }
    else  if (class ~= "(Cabinet|Explore)WClass") {
      for window in ComObjCreate("Shell.Application").Windows
        if  (window.hwnd==hwnd)
          sel :=  window.Document.SelectedItems
      for item in sel
      {
        FileFullPath:=item.path
        If (FileComponents=1)
          FileParts:=FileFullPath
        If (FileComponents=2)
          SplitPath,FileFullPath,,,,FileParts
        If (FileComponents=3)
          SplitPath,FileFullPath,FileParts
        If (GetDate>1)
        {
          ToReturn .= DateParse(item.ModifyDate) . A_Tab . FileParts . "`n"
          If (GetDate=2)
            Sort,ToReturn ; GetDate=2 --> modified date sorted ascending
          Else
            Sort,ToReturn,R ; GetDate=3 --> modified date sorted descending
        }
        else
          ToReturn .= FileParts "`n" ; GetDate=1 --> no date/sort
      }
    }
    return  Trim(ToReturn,"`n")
}

;====================================
;Function use to rename selected files
;====================================

RenameSelectedFile(hwnd:="",FileComponents:=1,GetDate:=1) {
; much of this function by sinkfaze: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=77#p395
; some of it by Joe Winograd for this EE question
  WinGet, process, processName, % "ahk_id" hwnd := hwnd? hwnd:WinExist("A")
  WinGetClass class, ahk_id %hwnd%
  if  (process = "explorer.exe")
    if  (class ~= "Progman|WorkerW") {
      ControlGet, files, List, Selected Col1, SysListView321, ahk_class %class%
      Loop, Parse, files, `n, `r
        ToReturn .= A_Desktop "\" A_LoopField "`n"
    }
    else  if (class ~= "(Cabinet|Explore)WClass") {
      for window in ComObjCreate("Shell.Application").Windows
        if  (window.hwnd==hwnd)
          sel :=  window.Document.SelectedItems
      for item in sel
      {
        FileFullPath:=item.path
        SplitPath,FileFullPath,FileNameWithExt,FilePath,FileExt,FileNameNoExt,FileDrive
        FileNameNoExt:=RegExReplace(FileNameNoExt,"[\x20\x60\!\#\$\%\&\'\(\)\+\,\.\;\=\@\[\]\^\_\{\}\~]","-") ; replace special chars with hyphen, but only in FileNameNoExt
        StringLower,FileNameNoExt,FileNameNoExt,T ; make all letters lower case except for first letter (this works because there are no spaces...hyphens replaced them)
        FileFullPathNew:=FilePath . "\" . FileNameNoExt . "." . FileExt
        If (FileExist(FileFullPathNew))
        {
          FileNameNoExt:=FileNameNoExt . "-" . A_Now ; if files exists, add YYYYMMDDhhmmss to file name (preceded by a hyphen)
          FileFullPathNew:=FilePath . "\" . FileNameNoExt . "." . FileExt
        }
        FileMove,%FileFullPath%,%FileFullPathNew%
        If (ErrorLevel!=0)
          MsgBox,4144,Rename Error,Error Level=%ErrorLevel% trying to rename:`n%FileFullPath%`nTo:`n%FileFullPathNew%
        If (FileComponents=1)
          FileParts:=FileFullPathNew
        If (FileComponents=2)
          FileParts:=FileNameNoExt
        If (FileComponents=3)
          FileParts:=FileNameNoExt . "." . FileExt
        If (GetDate>1)
        {
          ToReturn .= DateParse(item.ModifyDate) . A_Tab . FileParts . "`n"
          If (GetDate=2)
            Sort,ToReturn ; GetDate=2 --> modified date sorted ascending
          Else
            Sort,ToReturn,R ; GetDate=3 --> modified date sorted descending
        }
        else
          ToReturn .= FileParts "`n" ; GetDate=1 --> no date/sort
      }
    }
    return  Trim(ToReturn,"`n")
}

;~ ;====================================
;~ ;Function to get date format used for sort by date and related to GetSelectFile function
;~ ;====================================

DateParse(str) {
; this function by polyethene: https://autohotkey.com/board/topic/18760-date-parser-convert-any-date-format-to-yyyymmddhh24miss/
  static e2 = "i)(?:(\d{1,2}+)[\s\.\-\/,]+)?(\d{1,2}|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*)[\s\.\-\/,]+(\d{2,4})"
  str := RegExReplace(str, "((?:" . SubStr(e2, 42, 47) . ")\w*)(\s*)(\d{1,2})\b", "$3$2$1", "", 1)
  If RegExMatch(str, "i)^\s*(?:(\d{4})([\s\-:\/])(\d{1,2})\2(\d{1,2}))?"
    . "(?:\s*[T\s](\d{1,2})([\s\-:\/])(\d{1,2})(?:\6(\d{1,2})\s*(?:(Z)|(\+|\-)?"
    . "(\d{1,2})\6(\d{1,2})(?:\6(\d{1,2}))?)?)?)?\s*$", i)
    d3 := i1, d2 := i3, d1 := i4, t1 := i5, t2 := i7, t3 := i8
  Else If !RegExMatch(str, "^\W*(\d{1,2}+)(\d{2})\W*$", t)
    RegExMatch(str, "i)(\d{1,2})\s*:\s*(\d{1,2})(?:\s*(\d{1,2}))?(?:\s*([ap]m))?", t)
      , RegExMatch(str, e2, d)
  f = %A_FormatFloat%
  SetFormat, Float, 02.0
  d := (d3 ? (StrLen(d3) = 2 ? 20 : "") . d3 : A_YYYY)
    . ((d2 := d2 + 0 ? d2 : (InStr(e2, SubStr(d2, 1, 3)) - 40) // 4 + 1.0) > 0
      ? d2 + 0.0 : A_MM) . ((d1 += 0.0) ? d1 : A_DD) . t1
      + (t1 = 12 ? t4 = "am" ? -12.0 : 0.0 : t4 = "am" ? 0.0 : 12.0) . t2 + 0.0 . t3 + 0.0
  SetFormat, Float, %f%
  Return, d
}

;~ ;====================================
;~ ;Function to get attributes related to usb to eject
;~ ;AutoHotkey: Eject all USB drivers
;~ ;====================================

Eject(DRV)
{
  Local  STORAGE_DEVICE_NUMBER, IOCTL_STORAGE_GET_DEVICE_NUMBER:=0x2D1080
  Local  OPEN_EXISTING:=3, hVol:=0, sPHDRV:="", qStr:="", qEnum:= "", nDID:=0, nVT:=1
  Local  AMT:="[Removable Media][External hard disk media]", dObj:={}, VT, VAR

  hVol:=DllCall("CreateFile", "Str","\\.\" . (DRV:=SubStr(DRV,1,1) . ":"), "UInt",0
               ,"UInt",0, "Ptr",0,"UInt",OPEN_EXISTING, "UInt",0, "Ptr",0, "Ptr")
  If (hVol=-1)
  {
    ErrorLevel:=FileExist(DRV) ? "Mapped/substitute drive" : "Invalid drive letter"
    Return dObj
  }

  VarSetcapacity(STORAGE_DEVICE_NUMBER,12,0)
  DllCall("DeviceIoControl", "Ptr",hVol, "UInt",IOCTL_STORAGE_GET_DEVICE_NUMBER
         ,"Int",0, "Int",0, "Ptr",&STORAGE_DEVICE_NUMBER, "Int",12, "PtrP",0, "Ptr",0)
  DllCall( "CloseHandle", "Ptr",hVol )

  sPHDRV := "\\\\.\\PHYSICALDRIVE" . NumGet(STORAGE_DEVICE_NUMBER,4,"UInt")
  qStr   := "Select * from Win32_DiskDrive where DeviceID='$$$'"
  qEnum  := ComObjGet("winmgmts:").ExecQuery(StrReplace(qStr,"$$$",sPHDRV))._NewEnum()
  qEnum[dObj]

  If !(DllCall("GetModuleHandle", "Str","SetupAPI.dll", "Ptr"))
  {
    DllCall("LoadLibrary", "Str","SetupAPI.dll", "Ptr")
  }
  DllCall("SetupAPI\CM_Locate_DevNode", "PtrP",nDID, "Str",dObj.PNPDeviceID, "Int",0)
  DllCall("SetupAPI\CM_Get_Parent", "PtrP",nDID, "UInt",nDID, "Int",0)

  VarSetCapacity(VAR,520,0)
  DllCall("SetupAPI\CM_Request_Device_Eject"
          ,"UInt",nDID, "PtrP",nVT,"Str",VAR, "Int",260, "Int",0)

  ErrorLevel:=(nVT=0 ? 0 : ["PNP_VetoTypeUnknown`nThe specified operation was reje"
  . "cted for an unknown reason.","PNP_VetoLegacyDevice`nThe device does not support "
  . "the specified PnP operation.","PNP_VetoPendingClose`nThe specified operation can"
  . "not be completed because of a pending close operation.","PNP_VetoWindowsApp`nA M"
  . "icrosoft Win32 application vetoed the specified operation.","PNP_VetoWindowsServ"
  . "ice`nA Win32 service vetoed the specified operation.","PNP_VetoOutstandingOpen`n"
  . "The requested operation was rejected because of outstanding open handles.","PNP_"
  . "VetoDevice`nThe device supports the specified operation, but the device rejected"
  . " the operation.","PNP_VetoDriver`nThe driver supports the specified operation, b"
  . "ut the driver rejected the operation.","PNP_VetoIllegalDeviceRequest`nThe device"
  . " does not support the specified operation.","PNP_VetoInsufficientPower`nThere is"
  . " insufficient power to perform the requested operation.","PNP_VetoNonDisableable"
  . "`nThe device cannot be disabled.","PNP_VetoLegacyDriver`nThe driver does not sup"
  . "port the specified PnP operation.","PNP_VetoInsufficientRights`nThe caller has i"
  . "nsufficient privileges to complete the operation.","PNP_VetoAlreadyRemoved`nThe "
  . "device has been already removed"][nVT] )

  Return dObj
}

;~ ;====================================
;~ ;Function to get attributes related to usb to eject
;~ ;AutoHotkey: Eject all USB drivers
;~ ;====================================

PhysicalFromLogical(d)
; Given a drive letter like "F" return the physical drive associated with it, i.e., \\\\.\\PHYSICALDRIVE2
{
  drv:=d . ":" ; m0h4n fix
  wmi := ComObjGet("winmgmts:")
  for LogicalDisk in wmi.ExecQuery("Select * from Win32_LogicalDiskToPartition")
    if InStr(LogicalDisk.Dependent,drv)
      for Partition in wmi.ExecQuery("Select * from Win32_DiskDriveToDiskPartition")
        if (Partition.Dependent = LogicalDisk.Antecedent) {
          Start := InStr(Partition.Antecedent, """") + 1
          return SubStr(Partition.Antecedent, Start, -1)
        }
  return 0
}

;~ ;====================================
;~ ;Function to get attributes related to usb to eject
;~ ;AutoHotkey: Eject all USB drivers
;~ ;====================================

GetInterface(pd)
; Given a drive path like \\\\.\\PHYSICALDRIVE2 return the drive's interface type, e.g., "USB"
{
  wmi := ComObjGet("winmgmts:")
  for Drive in wmi.ExecQuery("Select * from Win32_DiskDrive where DeviceId = """ pd """")
    return Drive.InterfaceType
  return 0
}

Open in new window

Complex functions and I don't want to take the risk to do any modification prior to consult you. Could you please let me know your thoughts on this and help me to review them if necessary. Thank you for your help.
> should I also release them as for the other scripts?
> please let me know your thoughts on this

It is good programming practice to release an object when you are done using it. Note this comment at the AutoHotkey COM doc (copied here under "Fair Use"):
The script must typically call ObjRelease() when it is finished with the pointer.
That said, AutoHotkey uses a reference-counting method to free automatically the resources used by an object when the script no longer references it. Also, AutoHotkey automatically releases (immediately after use) a temporary object that was created within an expression, i.e., an object that was not explicitly stored. For example, suppose you create an Outlook object with this:

oOutlook:=ComObjActive("Outlook.Application")

Open in new window

That object is explicitly stored in the oOutlook variable and it is good practice to release it with this:

ObjRelease(oOutlook)

Open in new window

But let's say you have this line in the script:

If (oOutlook.ActiveExplorer.Selection.Item(1).Class!=43)

Open in new window

That creates the temporary object oOutlook.ActiveExplorer.Selection.Item(1) within that expression, and AutoHotkey will automatically release it. However, let's say, for more clarity in your code, that you did this:

oItem:=oOutlook.ActiveExplorer.Selection.Item(1)
ItemClass:=oItem.Class
If (ItemClass!=43)

Open in new window

Then it would be good practice to do this:

ObjRelease(oItem)

Open in new window

Regards, Joe
Thank you Joe for this explanation.
Last question concerning ObjRelease. Is there a potential risk specially on performance at two levels: 1) on the script execution it self and on process related to my computer?
> Is there a potential risk specially on performance at two levels

> on the script execution itself

The risk is in resources not being freed up (memory leaks) if you don't call ObjRelease, but I doubt that you'll have a perceptible difference in script performance one way or the other.

> on process related to my computer

Similar answer to above, although memory leaks over a very long period of time (with many executions of the script where ObjRelease is not called) could result in a performance hit.

Regards, Joe
Ok, noted thank you very much for your feedback.

Regards,
Luis.