I haven't used AHK before so it's probably a typographical error but would love any help. I'm trying to click into the search bar on File Explorer, search for the phrase .pdf and then select all of the files. Then I want to click the back button so I can go to the parent folder and then paste the files. Lastly, I want to click home and then click the folder at the top and delete it. The purpose for all of this is to take all the PDFs out of the subfolders and put them into the main folder, and then delete the subfolders.
I've tried this.
^e::
Click 3510,201
Send, *.pdf*{Enter} ^a ^x
Click 2601,200
^v
Send {Home}
Click 2896,266
Send {Delete}
Return
Currently, when I try it, it deletes all of the files in my folder.
There appear to be a few issues with your code and addressing each may make your code work, but I suggest a different, more robust, approach.
Try using a file-loop to find all files in a location (and sub-locations) with the PDF extension and FileMove to move these to your desired location.
https://www.autohotkey.com/docs/commands/LoopFile.htm
https://www.autohotkey.com/docs/commands/FileMove.htm
Edit (per comments)
There are a few ways to grab the path from file explorer. The easiest I can think is to send a ^{f4} which will select the full path, from there you can send a ^c copy it to the clipboard and use it for the file-loop path.
Edit2 (working example)
f1::
WinGetClass , vClass , A
If !(vClass = "CabinetWClass") ; Stops here if file explorer isn't the active window
Return
clipboard := ""
Send , ^{f4}{esc}^c
ClipWait , 1
If ErrorLevel ; Stops here if nothing was copied
Return
Loop , Files , % clipboard . "\*.pdf" , R
FileMove , %A_LoopFileLongPath% , %clipboard%
Return
Note that this will fail for named locations like "Documents", Downloads", or "This PC"; it must be a full path to work. Also, this will not delete the subfolders, but see FileRemoveDir for help with that.
https://www.autohotkey.com/docs/commands/FileRemoveDir.htm
Edit3 (using FileRemoveDir to delete empty folders)
This snippet will delete empty folders, starting with the deepest level and working to the top level. I take no credit for this as it by SKAN from the original AHK forums.
SetBatchLines -1
FileSelectFolder, Folder, , % (Del:=0), Purge Empty Folders
If ( ErrorLevel Or Folder="" )
Return
Loop, %Folder%\*, 2, 1
FL .= ((FL<>"") ? "`n" : "" ) A_LoopFileFullPath
Sort, FL, R D`n ; Arrange folder-paths inside-out
Loop, Parse, FL, `n
{
FileRemoveDir, %A_LoopField% ; Do not remove the folder unless is empty
If ! ErrorLevel
Del := Del+1, RFL .= ((RFL<>"") ? "`n" : "" ) A_LoopField
}
MsgBox, 64, Empty Folders Purged : %Del%, %RFL%
This can be adapted to fit the example from Edit2 and you should be good to go!
Related
I'm working in multiple JIRA systems, multiple projects within JIRAs, etc. I thought I had this working, but even when I select only one JIRA instance, it still tries to search.
The items I work with are in the form ABC-1234 (Where ABC is a various alpha key for the project - HWB, OFS, DCD, etc, and 1234 is the number of the item within the project.)
Unfortunately, they don't link well between the two different JIRA systems except for a simple text field. I made this to help me bounce between them, and also be able to immediately open items when someone mentions an item in email or Teams.
My issue is that it ALWAYS opens as a search (/issues/ instead of /browse/). And I can't figure out how it's getting into those If statements.
^+c:: ; Ctrl + Shift + c
originalclipboard := clipboard ; Save Original Clipboard contents
clipboard := "" ; empty the clipboard
send ^{c} ; Copy highlighted text
Clipwait ; Wait for clipboard to contain text
sleep 100
If InStr(clipboard,"HWB",false) ; Will affect if we're working with external or internal JIRA URLs later. Determined by JIRA key
jira = internal
If (InStr(clipboard,"OFS",false) or InStr(clipboard,"BAS",false) or InStr(clipboard,"DAS",false) or InStr(clipboard,"DCD",false))
jira = external
If InStr(clipboard,",",false) ; If we're just working with a comma delimited list
{
clipboard := StrReplace(clipboard,",","%2C") ; replace commas with URL language
clipboard = ?jql=key`%20in`%20(%clipboard%) ; add in the JQL search language
if jira = internal
Run https://jira.fis.dev/issues/%clipboard% ;internal URL for search
if jira = external
Run https://hwbdigitalstrategy.atlassian.net/issues/%clipboard% ;external URL for search
clipboard := originalclipboard ; Replace original clipboard contents
return
exit
}
If InStr(clipboard,"`r`n",false) ; If working with items on different lines (Excel or document)
{
clipboard := StrReplace(clipboard,"`r`n","%2C") ; replace newline character with commas in URL language
}
clipboard = ?jql=key`%20in`%20(%clipboard%) ; add in the JQL search language
if instr(clipboard,"%2C)"){ ; Error fixing - if the last character is comma before the ) remove the comma
clipboard := StrReplace(clipboard,"%2C)",")")
if jira = internal
Run https://jira.fis.dev/issues/%clipboard%
if jira = external
Run https://hwbdigitalstrategy.atlassian.net/issues/%clipboard%
clipboard := originalclipboard ; Replace original clipboard contents
return
exit
}
else
{
If jira = internal ; This kicks off if there was no commas or newline characters - one JIRA item will open straight into the item instead of searching
Run https://jira.fis.dev/browse/%clipboard%
if jira = external
Run https://hwbdigitalstrategy.atlassian.net/browse/%clipboard%
}
clipboard := originalclipboard ; Replace original clipboard contents
return
In C:\Windows\System32\drivers\etc, I have the standard hosts file
In C:\Windows\System32\drivers\etc, I have a file named hosts-backup.txt
Using autohotkey, when I press `abc1, I'd like to replace the hosts file with the contents of the file named hosts-backup.txt
Here's what I have so far:
`::
Check := true
SetTimer, CheckOff, 1000 ; 1 second to type in 001
return
:*:abc1::
If Check
; Read the contents of the backup file
FileRead, Contents, C:\Windows\System32\drivers\etc\hosts-backup.txt
if not ErrorLevel ; Successfully loaded.
{
ListVars
Pause
; delete the old hots file
FileDelete, C:\Windows\System32\drivers\etc\hosts.txt
; also tried this with C:\Windows\System32\drivers\etc\hosts
; save a new copy of the hosts file with the content from the backup file
FileAppend, %Contents%, C:\Windows\System32\drivers\etc\hosts.txt
; also tried this with C:\Windows\System32\drivers\etc\hosts
Contents = ; Free the memory.
}
return
CheckOff:
Check := false
return
ListVars returns the following:
Global Variables (alphabetical)
--------------------------------------------------
0[1 of 3]: 0
Check[1 of 3]: 0
Contents[0 of 0]:
ErrorLevel[1 of 3]: 0
When the above script is run, the file at C:\Windows\System32\drivers\etc\hosts is not updated.
How can I fix this?
Update per Blauhirn's suggestions (still not working)
`::
Check := true
SetTimer, CheckOff, -1000 ; 1 second to type in abc1
return
:*:abc1::
If Check{
; Read the contents of the backup file
FileRead, Contents, C:\Windows\System32\drivers\etc\hosts-backup.txt
if not ErrorLevel ; Successfully loaded.
{
ListVars
; delete the old hots file
FileDelete, C:\Windows\System32\drivers\etc\hosts.txt
; save a new copy of the hosts file with the content from the backup file
FileAppend, %Contents%, C:\Windows\System32\drivers\etc\hosts.txt
Contents = ; Free the memory.
}
}
return
CheckOff:
Check := false
return
Update
This appears to be a permissions issue, if I compile the script to an exe and run it as an administrator, it works as expected.
Is there a simple way around this using autohotkey?
This piece of code of yours:
`::
Check := true
SetTimer, CheckOff, 1000 ; 1 second to type in 001
return
CheckOff:
Check := false
return
Will set check to false every 1000 ms repeatedly. "Specify a negative Period to indicate that the timer should run only once". Since it doesn't get set back to true anywhere in your code automatically, this whole timer thingy is kind of useless. What is it you want to achieve with it? Check will only be true within the first 1 second. Thus,
If Check
FileRead, Contents, C:\Windows\System32\drivers\hosts-backup.txt
Will not do anything after the second is over. Also be aware that the if-clause only works for the next line, because you're not using any braces { }.
edit
I might be wrong, but seems that you can spare the timer and simply make a hotstring out of it: (two `` because backtick is the escape character in ahk)
:*:``abc1::
; Read the contents of the backup file
FileRead, Contents, C:\Windows\System32\drivers\etc\hosts-backup.txt
if not ErrorLevel ; Successfully loaded.
{
ListVars
Pause
; delete the old hots file
FileDelete, C:\Windows\System32\drivers\etc\hosts.txt
; also tried this with C:\Windows\System32\drivers\etc\hosts
; save a new copy of the hosts file with the content from the backup file
FileAppend, %Contents%, C:\Windows\System32\drivers\etc\hosts.txt
; also tried this with C:\Windows\System32\drivers\etc\hosts
Contents = ; Free the memory.
}
return
Seems logical to me that only Administrators may edit the system32 folder.. have you tried changing the AutoHotkey.exe to "run ALWAYS as Administrator"? http://technet.microsoft.com/en-us/magazine/ff431742.aspx
I'm trying to find a way to empty a text file with Autohotkey. Unfortunetely I was unable to find any build in function that allowes you to do just that. The best solution I could come up with would be:
FileRead, TheText, file.txt
StringReplace, NewText, TheText, 'text to search for', 'replacement text', All
FileDelete, file.txt
FileAppend, %NewText%, file.txt
However, this solution won't work for me because I'm writing a tool that will add or delete content from the Windows hosts file. Deleting the hosts file is out of the question ofcourse.
So instead I'm loading the hosts file content into a variable, replace any addition made by my program with an empty string (to restore the original hosts file content) inside this variable and write it back to the hosts file. But in order to do that I need to empty the file first or it would just append the variable to the existing content.
For those who are curious about my program:
The program will enable/disable the automatic Skype login feature on outlook.com or hotmail.com. Ofcourse without cauing any problems for the mail functions or the Skype client.
Here is a way using a fileobject (req ahk 1.1+)
just put this example in a new script file and run it to see how it works
/* String1
* String2
* String3
* String4
* String5
* String6
* String7
* String8
* String10
*/
msgbox % CutString(A_ScriptFullPath, "String7")
CutString(filePath, Needle)
{
fileObj := FileOpen(filePath, "rw"), pointerPos := fileObj.Pos, RegExMatch(fileObj.read(), "O)" needle, match)
fileObj.Seek(0), newText .= fileObj.read(match.pos-1), fileObj.Seek(match.pos + match.Len), newText .= fileObj.read()
fileObj.Seek(pointerPos), fileObj.Write(newText), fileObj.length := StrLen(newText), fileObj.Close()
return match.value
}
This function uses RegEx to find the pos and size of "needle" string in a file and then rewrites and resizes the file content without it.
You can also rewrite the function to simply cut out lines from the file.
I hope this does what your need
What about renaming it? I've done this before.
hosts := "c:\windows\system32\etc\hosts"
fileread, hostsText, %hosts%
stringreplace, newText, %hostsText%, %searchtext%, %replacementtext%, all
fileappend, %hostsText%, %hosts%.bak ;make backup
filedelete, %hosts%.temp ;delete the temp file if it exists
fileappend, %newtext%, %hosts%.temp ;write new text to a blank file
filemove, %hosts%.temp, %hosts%, 1 ;rename the new file to hosts - set flag to 1 to overwrite
Then you need to flush the DNS, no?
Run cmd /c ipconfig /flushdns
Follow the below steps:
Run, notepad.exe invalid_response.txt
Sleep, 200
Send, ^a
Send, {Del}
Sleep, 200
Send, !{F4}
Send, {HOME}
Sendraw, #
Send, ^s
Sleep, 200
Send, !{F4}
Exit
I have the below simple code, which sends keystrokes for text in the clipboard with a 15ms delay in between characters (I use this to traverse huge lists of treeview elements).
Issue: If I have copied 'text1' to clipboard, followed by 'text2', this script outputs 'text1text2' instead of 'text2' alone.
If I reload the script, then it prints 'text2'.
Is there a mistake in the below code, or is it a bug in implementing %clipboard% in Autohotkey 1.1.14.03 ?
#v::
textToType=" "
textToType=%clipboard%
LoopCount:=StrLen(textToType)
;StringLen, LoopCount, textToType
Array%LoopCount%:=textToType
loop %LoopCount%
{
theChar:=Array%A_Index%
Send %theChar%
sleep 15
}
return
Update: Thanks for pointing out smarter ways of doing this, but I would still like to figure out what is wrong in the above piece of code.
Update 2:
The mistake was in my understanding of the AHK syntax. Array%LoopCount%:=textToType assigns the whole string value in textToType to the (LoopCount)th STRING element of the STRING array named 'Array'.
Update 3:
(Thanks #John Y for clarifying)
Actually, there's no "declared" array at all, in a traditional sense. You just have a bunch of individual variables, dynamically created as needed, that happen to have names with numbers at the end. Array1 and Array2 are not elements in some Array object. They are just two completely independent variables. AutoHotkey provides a way to glue numbers onto the ends of names, so you can use them like an array.
The reason your script doesn't work properly is because you're using a pseudo-array to store different words from your clipboard.
I've commented up your code to explain what it does:
#v::
textToType := "" ; Empty variable
textToType := Clipboard ; Move clipboard into variable
; Get lenght of the word
; Used as array index / loop count
LoopCount := StrLen(textToType)
; Put the clipboard in an array at index 'LoopCount'
Array%LoopCount% := textToType
; Loop through the array as many times
; as the string is long
Loop % LoopCount
{
; Retrieve the word at this index in the array
theChar := Array%A_Index%
; Send the whole word
Send, % theChar
sleep 15
}
return
Instead of sending each character at a time, you're sending whole words from specific indexes in your Array array.
Say you copy the word Dragon, that word is 6 letters long. So you'd put that in Array6, then you'd loop through your array 6 times using the same variable. At which point the loop would take each index at a time and move it into theChar. On your 6th lap in the loop you'd put Array6 into theChar and print the whole word at once.
Then you copy the word Stackoverflow. That's going to go into Array13, and we're going to loop 13 times. On the 6th lap we're going to print out Dragon which is in Array6, and then keep going until we reach 13 where we'll print Stackoverflow since that is in Array13.
So that's why your script isn't doing what you want it to. Hopefully this helps a little.
See the code sample alpha bravo posted, that's the correct way of achieving what you want to do.
keep it simple
#v::
loop Parse, Clipboard
{
Send %A_LoopField%
sleep 15
}
return
There must be a bug in implementation of clipboard assignment in AHK. With the below code, the behaviour of AHK is that everytime the value of dir is accessed, AHK fetches the latest value from clipboard, instead of fetching the value of dir at the time the script was activated.
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
sleep 100
dir := StrReplace(dir, "`r`n")
EDIT:
To fix this, I added 1 second sleep before clipboard assignment code:
sleep 1000
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
dir := StrReplace(dir, "`r`n")
100 millisecond sleep didn't seem to work.
Accessing value of dir now only gives value of last clipboard assignment at activation.
I’ve written script that contains numerous hotkeys (general structure is as below). I would like to create another one that when pressed displays a list of all of the hotkeys and their corresponding descriptions that the script contains in a nice, formatted table.
The formatting and display are tenuous since AutoHotkey’s output is limited to message-boxes, but possible. More problematic is getting the hotkeys and corresponding descriptions.
The hotkeys all call the same function with different arguments. I considered adding a variable to the function so that depending on the value, the function either performs the normal function when triggered by the normal hotkeys, or builds a string or something when triggered from the special display hotkey.
I cannot figure out a way to programmatically access the script’s hotkeys at all. I checked the docs and there don’t seem to be any A_ variables that can be used for this purpose, nor does the Hotkey command lend itself well (it can be used to test if a hotkey exists, but looping through the innumerable combinations is, at best, tedious).
Failed attempts:
I tried using Elliot’s suggestion of parsing the script itself (replacing the path with %A_ScriptFullPath%, and while it does work for a raw script, it does not when the script is compiled
I tried assigning the entire hotkey section of the script to a variable as a continuation section and then parsing the variable and creating hotkeys using the Hotkey command. This worked well right up until the last part because the Hotkey command cannot take arbitrary commands as the destination and requires existing labels.
The ListHotkeys command is not applicable because it only displays the hotkeys as plain text in the control window.
Does anyone know how I can display a list of the hotkeys and either their corresponding arguments or comments?
Example script:
SomeFunc(foobar)
{
MsgBox %foobar%
}
!^#A::SomeFunc("a") ; blah
^+NumpadMult::SomeFunc("c") ; blivet
^+!#`::SomeFunc("b") ; baz
^#Space::SomeFunc("d") ; ermahgerd
…
Example desired “outputs”:
C+A+ W+ A a | C+ S+ NumpadMult b
------------------+----------------------
C+A+S+W+ ` c | C+ W+ Space d
or
Ctrl Alt Shift Win Key Action
-----------------------------------------
× × × A blah
× × NumpadMult baz
× × × × ` blivet
× × Space ermahgerd
etc.
The only thing I can think of is to read each line of your script individually and parse it. This code reads your script (script.ahk) one line at a time and parses it. This should get you started. Additionally, you could parse the line to check for the modifiers as well.
Loop
{
FileReadLine, line, C:\script.ahk, %A_Index%
if ErrorLevel
break
If Instr(line, "::")
{
StringSplit, linearray, line, ::,
key := linearray1
StringSplit, commandarray, linearray3, `;
action := commandarray2
hotkeyline := "key: " . key . "`tAction: " . action
final .= hotkeyline . "`r"
}
}
msgbox % final
return
I found a solution. It is not perfect (or ideal), and hopefully a proper, built-in method will become available in the future, but it works well (enough) and for raw and compiled scripts.
What I did was to use the FileInstall command which tells the compiler to add a file to the executable (and extract it when run).
Sadly, the FileInstall command will not allow the use of variables for the source file, so I cannot simply include the script itself (FileInstall, %A_ScriptFullPath%, %A_Temp%\%A_ScriptName%, 1).
As a work-around, I ended up extracting all of the desired hotkeys to a second file which I then parse as Elliot suggested, then delete, and #Include at the end of my script (it must be at the end since hotkeys will terminate the autoexecute section).
;;;;; Test.ahk ;;;;;
; Add hotkey file to executable and extract to Temp directory at runtime
FileInstall, Hotkeys.ahk, %A_Temp%\Hotkeys.ahk, 1
Loop
{
;Read a line from the extracted hotkey script and quit if error
FileReadLine, line, %A_Temp%\Hotkeys.ahk, %A_Index%
if ErrorLevel
break
;Trim whitespace
line=%line%
; Parse the line as in Elliot’s answer, but with tweaks as necessary
ParseHotkey(line)
…
}
FileDelete, %A_Temp%\Hotkeys.ahk ; Delete the extracted script
DisplayHotkeys() ; I ended up bulding and using a GUI instead
#Include, Hotkeys.ahk ; It is included at compile-time, so no A_Temp
;;;;; Hotkeys.ahk ;;;;;
z::MsgBox foo
y::MsgBox bar