I'm fairly new to autohotkey and ran into a problem recently when trying to pass variables as parameters for the "run" command in autohotkey. Can anyone show me what I'm missing or is this a bug?
Gui, Setup:Add, Edit, w100 vProgram
Gui, Setup:Add, Button, Default w100 gSubmit, OK
Gui, Setup:Show,, Setup
WinWaitClose, Setup
ExitApp
Submit:
Gui, Setup:Submit
RunStuff()
RunStuff() {
Run, %Program%
}
I've even tried changing the line to this:
RunStuff() {
run, Program
}
I would really appreciate the help, thanks in advance.
First things first, while
WinWaitClose, Setup
ExitApp
Is maybe smart thinking, you're supposed to do this with the GuiClose event by just defining a function (or a label) with the name GuiClose.
GuiClose()
{
ExitApp
}
Also, giving the gui a name is redundant, although if you're planning on adding more guis, fair enough.
And to run cmd commands, you'd start off cmd with the /c(docs) switch.
Your example netsh wlan connect ssid=%networkname% name=%networkname% would be done like this:
networkname := "name"
Run, %A_ComSpec% /c netsh wlan connect ssid=%networkname% name=%networkname%
Run, % A_ComSpec " /c netsh wlan connect ssid=" networkname " name=" networkname
The first line is in legacy syntax, I wouldn't really recommend it.
The second line is in expression syntax.
The built in variable A_ComSpec(docs) contains the path to cmd.exe.
Example program based on your code to open a file in notepad:
Gui, Setup:Add, Edit, w300 vFilePath, % "C:\Users\User\Desktop\this is a text file.txt"
Gui, Setup:Add, Button, Default w100 gSubmit, OK
Gui, Setup:Show, , Setup
return
Submit:
Gui, Setup:Submit
Run, % "notepad.exe """ FilePath """"
return
GuiClose()
{
ExitApp
}
Note how the file path includes spaces, so the argument needs to wrapped in quotes.
"""" may seem weird, but a quote is escaped with another quote in AHK. So outer quotes specify that you're writing the string, and inner "" is just one quote escaped. So this produces ".
Legacy syntax vs modern expression syntax can be a bit confusing when you're learning AHK. You'll see a lot of legacy syntax when you look up stuff. This is mainly because AHK was much more popular years and years ago (when legacy syntax was the thing to use).
To get started off on the legacy vs modern expression differences, here's a pretty good documentation page:
https://www.autohotkey.com/docs/Language.htm
EDIT:
Answer to the new problem that as added in via an edit to the OP.
RunStuff() {
Run, %Program%
}
The variable Program is not defined in the function's scope.
Lets consider this example code
var1 := 1
var2 := 2
global var3 := 3
function()
return
function()
{
global var2
MsgBox, % "var1: " var1 "`nvar2: " var2 "`nvar3: " var3 "`n"
}
var1 is not defined in the function's scope, so nothing will be printed in the message box.
var2 will be used from outside the function's scope because of the line global var2.
var3 would also be used from outside of the function scope because var3 was defined as super global. Super global isn't really recommended, because anything anywhere will use that variable then. Quite easy to run into problems on larger scripts, but it's convenient for smaller scripts where you know what you'll be doing and don't have any external libraries for example.
You can read the related documentation from here:
https://www.autohotkey.com/docs/Functions.htm#Locals
It seems that you think your problem is with the gui output.
I think you might want to check things in a different way:
You could create a script to analyze the your run inputs.
For example:
Script #1, let's call it "1.ahk"
Run, 2.ahk param1 param 2 "param 3"
Script #2, let's call it "2.ahk"
txt := "Params as seen by ahk:`n"
for i, param in A_Args
txt .= i " = " param "`n"
MsgBox % txt
By running script "1.ahk" you can see the message that "2.ahk" creates:
Params as seen by ahk:
1 = param1
2 = param
3 = 2
4 = param 3
Which I think it's quite telling about how it understands spaces, and how quotes prevent splitting arguments.
Now if you run your gui outputs against "2.ahk" you might see something different to what you saw before.
RunStuff() {
Run % start "" "C:\blabla\prog.exe" -windowstyle minimized
}
Related
I know I can dot source a file:
. .\MyFunctions.ps1
But, I would like to dot source the commands in a string variable:
. $myFuctions
I see that this is possible:
.{$x=2}
And $x equals 2 after the script block is sourced.
But... .{$myFunctions} does not work.
I tried $myFunctions | Invoke-Expression, but it doesn't keep the source function in the current scope. The closest I have been able to come up with is to write the variable to a temporary file, dot source the file, and then remove the file.
Inevitably, someone will ask: "What are you trying to do?" So here is my use case:
I want to obfuscate some functions I intend to call from another script. I don't want to obfuscate the master script, just my additional functions. I have a user base that will need to adjust the master script to their network, directory structure and other local factors, but I don't want certain functions modified. I would also like to protect the source code. So, an alternate question would be: What are some good ways to protect PowerShell script code?
I started with the idea that PowerShell will execute a Base64-encoded string, but only when passed on the command line with -EncodedCommand.
I first wanted to dot source an encoded command, but I couldn't figure that out. I then decided that it would be "obfuscated" enough for my purposes if I converted by Base64 file into a decode string and dot sourced the value of the string variable. However, without writing the decoded source to a file, I cannot figure out how to dot source it.
It would satisfy my needs if I could Import-Module -EncodedCommand .\MyEncodedFile.dat
Actually, there is a way to achieve that and you were almost there.
First, as you already stated, the source or dot operator works either by providing a path (as string) or a script block. See also: . (source or dot operator).
So, when trying to dot-source a string variable, PowerShell thinks it is a path. But, thanks to the possibility of dot-sourcing script blocks, you could do the following:
# Make sure everything is properly escaped.
$MyFunctions = "function Test-DotSourcing { Write-Host `"Worked`" }"
. { Invoke-Expression $MyFunctions }
Test-DotSourcing
And you successfully dot-sourced your functions from a string variable!
Explanation:
With Invoke-Expression the string is evaluated and run in the child scope (script block).
Then with . the evaluated expressions are added to the current scope.
See also:
Invoke-Expression
About scopes
While #dwettstein's answer is a viable approach using Invoke-Expression to handle the fact that the function is stored as a string, there are other approaches that seem to achieve the same result below.
One thing I'm not crystal clear on is the scoping itself, Invoke-Expression doesn't create a new scope so there isn't exactly a need to dot source at that point...
#Define your function as a string
PS> $MyUselessFunction = "function Test-WriteSomething { 'It works!' }"
#Invoke-Expression would let you use the function
PS> Invoke-Expression $MyUselessFunction
PS> Test-WriteSomething
It works!
#Dot sourcing works fine if you use a script block
PS> $ScriptBlock = [ScriptBlock]::Create($MyUselessFunction)
PS> . $ScriptBlock
PS> Test-WriteSomething
It works!
#Or just create the function as a script block initially
PS> $MyUselessFunction = {function Test-WriteSomething { 'It works!' }}
PS> . $MyUselessFunction
PS> Test-WriteSomething
It works!
In other words, there are probably a myriad of ways to get something similar to what you want - some of them documented, and some of them divined from the existing documentation. If your functions are defined as strings, then Invoke-Expression might be needed, or you can convert them into script blocks and dot source them.
At this time it is not possible to dot source a string variable.
I stand corrected! . { Invoke-Expression $MyFunctions } definitely works!
I'm trying to use [regex]::Replace with a match evaluator to selectively replace parts of a string. I'm writing and debugging the function in PowerShell ISE. What is strange is that running the replacement code causes one machine to output a string that is the content of the match evaluator script block while the other replaces the text correctly. I had no clue this was even possible nor why it is happening.
Given this code (borrowed from another stackoverflow answer):
$global_counter = 0
$callback = {
$global_counter += 1
"string-$($args[0])-" + $global_counter
}
$re = [regex]"match"
$re.Replace('zzz match match xxx', $callback)
Executing it on one machine causes the output (PowerShell Version 5.1.18362.145):
zzz string-match-1 string-match-1 xxx
But on another it outputs (PowerShell Version 5.1.17134.858):
zzz
$global_counter += 1
"string-$($args[0])-" + $global_counter
$global_counter += 1
"string-$($args[0])-" + $global_counter
xxx
Both are running in an x64 PowerShell ISE clean instance directly from reboot. Does anyone know why this is happening?
With debugging help from Jeroen I've managed to figure out why this is happening.
PowerShell has a security feature called Constrained Language Mode that prevents the use of any, but a core set of whitelisted types. What appears to be happening is that I'm defining a scriptblock that in turn is converted to a System.Text.RegularExpressions.MatchEvaluator before being passed to the Replace function. The match evaluator however is outside of this core set of types which means when the PowerShell engine tries to coerce the type onto an overload of Replace the only other valid one is Replace(string, string, string) (thanks Jeroen for pointing this out in the comments). The Replace function does its job, but with a regular string as a replacement thus resulting in the odd behaviour.
I'm not able to alter the language mode of my PowerShell session on the machine I'm currently working with as it is applied through Group Policies, but a workaround for me at least was to use an elevated PowerShell session and ISE to test my script.
I have little experience with PowerShell in particular.
I'm trying to refactor some very commonly re-used code into a single script that can be sourced where it's needed, instead of copying and pasting this same code into n different scripts.
The scenario I'm trying to get looks (I think) like this:
#common.ps1:
param(
# Sure'd be great if clients didn't need to know about these
$some_params_here
...
)
function Common-Func-Uses-Params {
...
}
⋮
# foo/bar/bat.ps1:
# sure would love not to have to redefine all the common params() here...
. common.ps1 <pass-the-arguments>
Common-Func-Uses-Params $specific_Foo/Bar/Bat_Data
As the pseudo-comments above indicate, I've only been able to do this so far by capturing the params in the calling script as well.
I want to be in a situation where I can update the common code (say with a -Debug or -DryRun or -Url or whatever parameter) and not have to worry about updating all of the client code to match.
Is this possible?
You're missing two key things:
args - which captures all of (and only) the unbound arguments to the script
splatting (#) - which is used to pass arrays or hashtables to a command rather than flattening them like you'd get with $
When you combine these, you can easily pass all arguments onto another script, like so:
# foo.ps1
. common.ps1 #args
With a sourced file like this:
#common.ps1
param ([string]$foo = "foo")
echo "`$foo is $foo"
You get these output:
> foo.ps1 returns $foo is foo
> foo.ps1 -Foo bar returns $foo is bar
Note that, if you're trying to use the PowerShell ISE it might take you a while to figure this out or debug any of it. When you're in the debugger, both $args nor $MyInvocation.UnboundArguments will do their best to hide that information from you. They'll appear to be completely empty.
You can print the args with >> echo "$(#args)", but that also provides the very weird side effect of telling the Debugger to continue. I think the splatting is adding an extra newline and that's ending up in the Command Window.
The best workaround I have for that is to add $theargs = $args at the top of your script and remember to use $theargs in the debugger.
I wrote a script analyzing perl-files (totally without PPI, because it will be used on Servers where the admins don't want PPI to be installed and so on and so forth, but let's not talk about that).
Now, let's say I have this code:
my $object = MySQL->new();
my $ob2 = $object;
$ob2->
(Where MySQL is one of our modules).
My script correctly identifies that $ob2 is a MySQL-Object and sees where it came from, and then returns a list of found subs in that module.
My idea was, that, since I use vim for editing, this could be a really cool way for "CTRL-n"-Completetion.
So, when...
$ob2->[CTRL-n]
It shows the CTRL-n-Box which opens my Perl-Script and gives it a few parameters (I would need: The line that I am actually on, the cursor position and the whole file as it is in vim).
I already found things like vim-perl, which allows me to write something like
if has('perl')
function DefPerl()
perl << EOF
use MyModule;
return call_to_my_function(); # returns all the methods from the object for example
EOF
endfunction
call DefPerl()
endif
But somehow this does not get executed (I tried writing something to a file with a system call just for the sake of testing)...
So, in short:
Does anyone here know how to achieve that? Calling a perl-function from vim by pressing CTRL-n with the full file-code and the line vim is actually in and the position, and then opening a completetion-menu with the results it got from the perl-script?
I hope someone knows what I mean here. Any help would be appreciated.
The details and tips for invoking embedded Perl code from Vim can be found in this Vim Tips Wiki article. Your attempts are already pretty close, but to return stuff from Perl, you need to use Vim's Perl API:
VIM::DoCommand "let retVal=". aMeaningfullThingToReturn
For the completion menu, your Perl code needs to return a List of Vim objects that adhere to the format as described by :help complete-items. And :help complete-functions shows how to trigger the completion. Basically, you define an insert-mode mapping that sets 'completefunc' and then trigger your function via <C-x><C-u>. Here's a skeleton to get your started:
function! ExampleComplete( findstart, base )
if a:findstart
" Locate the start of the keyword.
let l:startCol = searchpos('\k*\%#', 'bn', line('.'))[1]
if l:startCol == 0
let l:startCol = col('.')
endif
return l:startCol - 1 " Return byte index, not column.
else
" Find matches starting with a:base.
let l:matches = [{'word': 'example1'}, {'word': 'example2'}]
" TODO: Invoke your Perl function here, input: a:base, output: l:matches
return l:matches
endif
endfunction
function! ExampleCompleteExpr()
set completefunc=ExampleComplete
return "\<C-x>\<C-u>"
endfunction
inoremap <script> <expr> <Plug>(ExampleComplete) ExampleCompleteExpr()
if ! hasmapto('<Plug>(ExampleComplete)', 'i')
imap <C-x><C-z> <Plug>(ExampleComplete)
endif
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