([^<]+)',&n))) {
1294 | If (m[1] = "/") || (m[1] = "/download/") || (m[1] = "version.txt")
1295 | || (m[1] = "_AHK-binaries.zip") || (m[1] = "zip%20versions/") || InStr(m[1],"Ahk2Exe")
1296 | Continue
1297 | If InStr(m[1],".zip") {
1298 | item := Map("date",Trim(n[1]," `t"),"url",obj.url "/" m[1])
1299 | list[m[1]] := item
1300 | }
1301 | }
1302 | }
1303 | return list
1304 | }
1305 |
1306 | format_json(txt,filter:="") {
1307 | _map := jxon_load(&txt), idx := 1
1308 |
1309 | While _map["assets"].Has(idx) {
1310 | url := _map["assets"][idx]["browser_download_url"]
1311 | _file := (arr:=StrSplit(url,"/"))[arr.Length]
1312 | If InStr(url,filter)
1313 | return Map("date",_map["published_at"],"url" ,url,"file",_file)
1314 | idx++
1315 | }
1316 | }
1317 | }
1318 |
1319 | LaunchScript(hk:="") {
1320 | Global Settings
1321 | obj := {exe:0}
1322 |
1323 | If (sel := Explorer_GetSelection()) {
1324 | a := StrSplit(sel,"`n","`r")
1325 | Loop a.Length {
1326 | SplitPath a[A_index],,,&ext
1327 | If (ext != "ahk")
1328 | Continue
1329 |
1330 | obj := proc_script(a[A_index])
1331 | cmd := (obj.admin?"*RunAs ":"") obj.exe (obj.admin?" /restart ":" ") Chr(34) a[A_Index] Chr(34)
1332 | (obj.exe) ? Run(cmd) : ""
1333 | }
1334 | }
1335 |
1336 | return !!obj.exe
1337 | }
1338 |
1339 | LaunchCompiler(hk:="") {
1340 | If (sel := Explorer_GetSelection()) {
1341 | Loop Parse Explorer_GetSelection(), "`n", "`r"
1342 | {
1343 | SplitPath A_LoopField,,,&ext
1344 | If (ext != "ahk")
1345 | Continue
1346 | obj := proc_script(A_LoopField, true)
1347 | Run Chr(34) obj.exe Chr(34) " /in " Chr(34) A_LoopField Chr(34) " /gui"
1348 | }
1349 | }
1350 | }
1351 |
1352 | LaunchEditor(hk:="") {
1353 | Global Settings
1354 |
1355 | If (sel := Explorer_GetSelection()) {
1356 | Loop Parse , "`n", "`r"
1357 | {
1358 | SplitPath A_LoopField,,,&ext
1359 | If (ext != "ahk")
1360 | Continue
1361 | Run Chr(34) Settings["TextEditorPath"] Chr(34) " " Chr(34) A_LoopField Chr(34)
1362 | }
1363 | }
1364 | }
1365 |
1366 | dbg(_in) {
1367 | Loop Parse _in, "`n", "`r"
1368 | OutputDebug "AHK: " A_LoopField
1369 | }
1370 |
1371 | ; ====================================================================================
1372 | ; MButton: Runs the selected script(s).
1373 | ; SHIFT + MButton: Opens selected script(s) in text editor.
1374 | ; CTRL + MButton: Launches Ahk2Exe with selected script(s) filled in. One instance per selection.
1375 | ; ====================================================================================
1376 | #HotIf WinActive("ahk_exe explorer.exe")
1377 | MButton::LaunchScript(A_ThisHotkey)
1378 | +MButton::LaunchEditor(A_ThisHotkey)
1379 | ^MButton::LaunchCompiler(A_ThisHotkey)
1380 |
1381 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 TheArkive
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AHK Portable Installer
2 |
3 |
4 |
5 | README Updated on 2023-01-19
6 |
7 | ## Latest Updates
8 |
9 | * fixed execution of #Requires directive to filter by major version, as is done by AHK
10 | * fixed execution of "match" option
11 | * updated readme
12 |
13 | Links:
14 | * [Ahk2Exe](https://github.com/AutoHotkey/Ahk2Exe)
15 | * [Window Spy](https://github.com/AutoHotkey/AutoHotkeyUX/blob/main/WindowSpy.ahk)
16 |
17 | Special thanks to the following users for their help and/or works that I was able to derive from, which were crucial to making this script what it is today:
18 |
19 | * boiler
20 | * teadrinker
21 | * Rapte_Of_Suzaku
22 | * lexikos
23 | * DaveT1 - for having a keen eye to find what I missed
24 |
25 | ## Intro
26 |
27 | [Posted on AutoHotkey.com Forums](https://www.autohotkey.com/boards/viewtopic.php?f=6&t=73056)
28 |
29 | This is a portable install manager and allows multiple versions of AutoHotkey to coexist simultaneously. This script is meant to work with the portable `.zip` archives of AutoHotkey, NOT the setup `.exe` versions. It is written in AHK v2.
30 |
31 | You can use this program in 1 of 2 ways:
32 | 1) Change/Switch the active AHK EXE version when desired.
33 | 2) Run multiple versions of AHK simultaneously without switching by using the `#Requires` directive in your scripts. This program will choose the EXE based on that criteria.
34 |
35 | This program can be run as an installer, or as a portable program. As an installer, registry entries are written to the system to implement the `.ahk` file type association. In Fully Portable mode no registry modifications are performed, and the program must remain running in the background.
36 |
37 | If you want to use AutoHotkey_H with this program, you can. Simply download your desired release(s) of AutoHotkey_H and extract the ZIP to a subfolder in the `base folder`.
38 |
39 | The default `base folder` is in `%A_ScriptDir%\versions`.
40 |
41 | ## Features
42 | * Download/remove AHK versions directly from the UI.
43 | * Download Ahk2Exe automatically (updates for pre-existing installs are still manual).
44 | * Fully Portable Mode. See the `Fully Portable Mode` section below.
45 | * Associate the `.ahk` extension with any version of AHK. (not in portable mode)
46 | * Selectively choose which context-menu items appear in the context menu. (not in portable mode)
47 | * Associate any text editor with "Edit Script" in context menu easily. (use SHIFT + MButton in portable mode)
48 | * Define as many versions of AHK as you want to run in parallel.
49 | * Checks for updates when prompted, or does so automatically if enabled.
50 | * Displays the latest versions of AHK v1 and v2 (with internet connection only).
51 | * Displays currently active version of AHK.
52 | * Download official AutoHotkey releases directly from the UI.
53 | * Easily invoke `WindowSpy.ahk`, `help file`, and the `Ahk2Exe` compiler for the selected `base version` from the UI.
54 | * Edit templates for new AutoHotkey.ahk files from GUI. (not in portable mode)
55 | * Optionally use this program like "AHK-EXE-Switcher" instead of a multi-version Launcher.
56 |
57 | ## Basic Setup
58 |
59 | Grab the latest copy of AHK v2 (currently recommended is v2.0.2), copy the desired version of `AutoHotkey32/64.exe` into the script dir and rename it to `AHK_Portable_Installer.exe`. Always run this EXE file to launch the script.
60 |
61 | Now you can download AutoHotkey through the UI. Just click `Settings`, pick a major version from the DropDownList, then select/downlad your desired version(s). To remove a version of AHK, right click on an entry in the main list, and select `Remove this version` from the context menu.
62 |
63 | The AutoHotkey `base folder` is located in the script directory by default. It can be moved to another location if desired. The structure of the `base folder` is described below. All downloads are cached. You can open the `base folder` and `temp folder` from the Download tab in Settings.
64 |
65 | Example:
66 | ```
67 | C:\AutoHotkey\ <-- base folder -> place this anywhere you want, or leave it in the script folder
68 | \_OLD_\old_versions_here... <-- AHK folders here will not be parsed/listed
69 | \ahk version blah\... <-- AHK folders for each version
70 | \ahk_h another_version\... <-- AHK folders for each version
71 | \and_so_on\... <-- AHK folders for each version
72 | ```
73 |
74 | Each subfolder should have it's own copy of a `help file`, `WindowSpy.ahk`, and `Compiler` folder with `Ahk2Exe` and all necessary components (like `.bin` files, `mpress.exe`, and/or `upx.exe` as needed). Note, that development version of AHK are likely to not include the `Ahk2Exe Compiler` and `WindowSpy.ahk`. See the links in the "Latest Updates" section at the top of this page if you need these components.
75 |
76 | Now you need to decide how you want to use this program:
77 | 1) Change the version as needed to run different scripts (like AHK-EXE-Switcher).
78 | 2) Use the `#Requires` directive in your scripts (recommended). You will need to make small changes to your scripts, but you will be able to run multiple versions of AHK simultaneously without switching. Read more below.
79 | 3) You also need to decide if you want to use this program as an installer (registers the `.ahk` extension on the system) or if you want to run in Fully Portable mode.
80 |
81 | ## Installer Mode
82 |
83 | This is the default mode. Simply leave `Fully Portable Mode` unchecked. When installing, the `Install For` option in the `Basics` tab determines whether registry modifications are made for the user, or for `All Users` (the system). You can optionally install for `All Users`, but you need to run this program as Admin.
84 |
85 | The `Base Folder`, as described above, is located in `%A_ScriptDir%\versions` by default. You can move this anywhere you wish, and then specify that folder in the `Base Folder` option. Leaving this setting blank will use the default folder location.
86 |
87 | When you click `Install` the `base version` is set and the script writes registry entries for the `.ahk` extension, and context menu entries (such as Right-Click > New, and "Run Script", "Compile Scrpt", etc).
88 |
89 | ## Fully Portable Mode
90 |
91 | Go to Settings > Options tab and check the `Fully Portable Mode` checkbox. In this mode no registry entries are written. You can use the hotkeys below to run and edit scripts. After you select your desired version click the `Select` button to change the selected base version.
92 |
93 | Hotkeys for Fully Portable mode:
94 |
95 | |Hotkey |Description |
96 | | -------------:| ---------------------------------------------------- |
97 | |MButton |Runs SELECTED script files. |
98 | |Shift + MButton|Open script file in specified text editor. |
99 | |Ctrl + MButton |Open the compiler with the selected script pre-filled.|
100 |
101 | If you want to use Fully Portable Mode, then you will also want to consider the following settings in the Options tab:
102 |
103 | * Hide Tray Icon (maybe uncheck this - check the rest as desired)
104 | * Close to Tray
105 | * Minimize on Startup
106 | * Run on System Startup
107 |
108 | ## Using the #Requires directive
109 |
110 | Use the `#Requires` directive in your scripts to have the Launcher dynamically select which AHK EXE to use for running the script.
111 |
112 | ```
113 | SYNTAX: #Requires [product] [version] [; options]
114 | ```
115 |
116 | Available options:
117 |
118 | * 32-bit = use only 32-bit AHK version
119 | * 64-bit = use only 64-but AHK version
120 | * admin = try to elevate and use *RunAs verb to run as Admin
121 | * match = match the version number exactly in #Requires instead of ensuring minimum version is available.
122 |
123 | ```
124 | Note: "32-bit" and "64-bit" cannot be combined. Also when using the "match" option, the match must be exact. An entry like:
125 |
126 | #Requires AutoHotkey v1.1 ; match
127 |
128 | ... will not work, because version numbers are actually longer than that. Therefore no match will be found.
129 | ```
130 |
131 | Example:
132 | ```
133 | ----------------------------------------------------------------
134 | * Uses a136, with system bitness.
135 |
136 | #Requires AutoHotkey 2.0-a136
137 | ----------------------------------------------------------------
138 | * Uses the latest v2 alpha on the system and uses only 32-bit.
139 |
140 | #Requires AutoHotkey 2.0-a ; 32-bit
141 | ----------------------------------------------------------------
142 | * Uses v2.0.1 or greater and only 64-bit.
143 | * If using "64-bit" on a 32-bit system an error is thrown.
144 | * "match" ensures only version 2.0.1 is used, otherwise an error is thrown if not found.
145 |
146 | #Requires AutoHotkey 2.0.1 ; 64-bit match
147 | ----------------------------------------------------------------
148 | * Uses only v2 alpha a138, uses 64-bit and runs script as admin.
149 |
150 | #Requires AutoHotkey 2.0-a138 ; 64-bit admin
151 | ----------------------------------------------------------------
152 | * For versions of AutoHotkey that don't have
153 | the #Requires directive, simply comment
154 | out the directive but format it the same.
155 |
156 | ; #Requires AutoHotkey 1.1.33 ; 32-bit
157 | ----------------------------------------------------------------
158 | ```
159 |
160 | The `[product]` and `[version]` are required parameters in the `#Requires` directive. How the script selects the EXE to use is the same a the `#Requires` directive.
161 |
162 | Notes:
163 | * If you don't specify an exact version, the **latest version** available in the versions folder that matches the `#Requires` directive string is used.
164 | * If you don't specify a `#Requires` directive then the selected/installed `base version` is used.
165 | * Usage of options will further modify how the `#Requires` directive operates.
166 |
167 | ## Compiling Scripts
168 |
169 | The `#Requires` directive is also used to determine which compiler to use. The logic is the same as the section above. If no `#Requires` directive is used, then the selected `base version` compiler is used.
170 |
171 | ## Setting up Ahk2Exe
172 |
173 | All versions of AutoHotkey v1 come with a compiler (Ahk2Exe), `.bin` files, and `mpress.exe`. These versions of `Ahk2Exe` also work for AutoHotkey v2. Just copy `Ahk2Exe.exe` and `mpress.exe` from the AHK v1 compiler folder to the AHK v2 compiler folder. DO NOT copy the `.bin` files from the AHK v1 folder.
174 |
175 | The latest versions of AutoHotkey_H v1 and v2, as of this document update, both have their own separate updated compilers which contain `Ahk2Exe.exe`.
176 |
177 | So if you were to run all versions of AutoHotkey in parallel (in theory) your compiler setup for each version of AutoHotkey in your base folder should follow these general guidelines:
178 |
179 | * All AutoHotkey v1 and v2 folders should have the same latest release of Ahk2Exe, but the `.bin` files will be different.
180 | * All AutoHotkey_H v1 folders should have the same version compiler. Just replace the older compiler with the newer one.
181 | * All AutoHotkey_H v2 folders should have the same version compiler. Just replace the older compiler with the newer one.
182 |
183 | This setup will allow you to properly compile any version of AutoHotkey you wish to setup on your system.
184 |
185 | ## Options Tab - Install Options
186 |
187 | The below options pertain to using this program as an Installer only. All other options not discussed either pertain to the Context Menu, or to "Fully Portable Mode". I gave the below options arbitrary numbers for easy reference in this document.
188 |
189 | #### Option #1: `Add to PATH on Install`
190 |
191 | This option adds the script directory to the PATH environment variable. This setting follows the `Install For` option on the `Basics` tab. This allows the Launcher (`AHK_Portable_Installer.exe`) to be used on the command line. (See "Command Line Usage" below.)
192 |
193 | #### Option #2: `Copy Installed EXE to "AutoHotkey.exe" on Install`
194 |
195 | If this option and Option #1 are enabled, then the installed AHK EXE will be copied to the script directory as `AutoHotkey.exe`. You can then use this on the command line according to the AutoHotkey docs.
196 |
197 | Checking this option alone, while copying the installed EXE to the script directory, will basically have no worth while effect on the system. Normally this option is used with Option #1, Option #3, or both.
198 |
199 | #### Option #3: `Register "AutoHotkey.exe" with .ahk files instead of Launcher on Install`
200 |
201 | If this option is enabled, then the usage of the `#Requires` directive as explained above will have no effect. Only the installed/selected version of AutoHotkey will be used to run `.ahk` scripts. This effectively turns this program into an "EXE switcher" similar to "AHK-EXE-Swapper".
202 |
203 | If you check this option, Option #2 will be automatically checked. If you uncheck Option #2, then this option will be automatically unchecked.
204 |
205 | ## Launcher Command Line Usage (AHK_Portable_Installer.exe)
206 |
207 | Usage:
208 |
209 | ```
210 | AHK_Portable_Installer.exe [Behavior] "path\to\script.ahk" [params...]
211 |
212 | [Behavior] and [params...] are optional.
213 |
214 | Simple usage:
215 | AHK_Portable_Installer.exe "path\to\script.ahk" [params...]
216 |
217 | Specify Behavior:
218 | AHK_Portable_Installer.exe [Launch|LaunchAdmin|Compiler] "path\to\script.ahk" [params...]
219 | ```
220 |
221 | If the first parameter is an `.ahk` script file, then the default behavior is `Launch`. If the behavior is specified as `Launch` and the `Admin` option is used on the `#Requires` directive within the script, then the script will still be launched as admin.
222 |
223 | Place parameters to be passed to the script after the `path\to\scrpt.ahk` parameter as usual.
224 |
225 | When launching scripts on the command line with `AHK_Portable_Installer.exe` , the script will be launched with an EXE according to the #Requires directive. If no `#Requires` directive is found, then the script will be launched by the installed/selectd EXE from the main UI.
226 |
227 | ## What AHK Portable Installer does NOT do...
228 |
229 | This is a PORTABLE installer, so this script:
230 |
231 | * WILL NOT write or remove registry entries that deal with modifying the App list of installed programs.
232 | * WILL NOT create a separate `.ahk2` extension or any other extension besides `.ahk`.
233 |
234 | ## Troubleshooting and avoiding problems
235 |
236 | If you need help post on the forums, or [visit the join #ahk on IRC or visit the AutoHotkey Discord](https://www.autohotkey.com/boards/viewtopic.php?f=76&t=59&p=406501&hilit=irc#p406501). I frequently go on IRC, and I'm usually always connected on Discord. Let me know what setup issues you are having and I'll see what I can do.
237 |
238 | Otherwise here are some basics to keep in mind:
239 |
240 | 1) It is NOT recommended to run this script along side a normal installation of AutoHotkey with the setup program, it is however theoretically possible. But this script will override the proper install with its own settings in the registry. This will render the Uninstall option of your legit install inoperable. You will need to reinstall your legit install in order to do a normal removal from the "Installed Programs" list in the Windows Control Panel / App Settings.
241 |
242 | 2) If you move your `base folder`, then you must "re-install" your chosen AutoHotkey `base version`. Simply click `Install/Select` again to update the setting. Keep in mind, if you don't specify a custom `base folder`, and you move the location of AHK Portable Installer, then you are also moving the `base folder` to the default location which is:\
243 | \
244 | `%A_ScriptDir%\versions`
245 |
246 | 3) Every version folder of AutoHotkey should have its own `help file`, `WindowSpy.ahk`, and `Compiler` folder with your desired compiler components.
247 |
248 | 4) You should generally always use the latest Ahk2Exe. Remember there are 3 different Ahk2Exe's:
249 | * AutoHotkey v1 and v2 use the same version.
250 | * AutoHotkey_H v1 has it's own compiler. The latest release will work for all verions, just replace the olders ones with the latest.
251 | * AutoHotkey_H v2 has it's own compiler. The latest release will work for all verions, just replace the olders ones with the latest.
252 |
253 | 5) In regards to `A_AhkPath`:
254 |
255 | For older compiled scripts, this is usually pulled from the registry. If you are managing multiple versions of AHK using this tool, remember that the `base version` selected from the main GUI is the only one written to the registry.
256 |
257 | 6) Running scripts in Fully Portable Mode:
258 | You must first `single click` with the `left mouse button` to select a script, and THEN you can `middle click` to run the script. Usually after running a script it is not uncommon for the selection to be undone, especially when scripts are on the desktop. So just remember, SELECT the script first (single left click) then `middle click` to run, every time. If the file is already selected you can just `middle click` to run.
259 |
260 | 7) Wrong PID (Process ID) when using Run/RunWait:\
261 | This is something you will encounter if you use `Run(my_script.ahk,,,&pid)`. Since this program is the actual launcher for the script, this is what is happening:
262 | * User script invokes `Run(my_script.ahk,,,&pid)`.
263 | * Registry queries file type association.
264 | * Launcher is invoked. <-- this is PID saved in &pid
265 | * Launcher determines which version of AHK to use.
266 | * Launcher uses `Run()` to launch the script which has a different PID.
267 |
268 | So the PID you are getting is that of the Launcher, not the actual script. I'm afraid there is no way around this, but generally there are better ways of interacting with a program (or an AHK script) than using the PID.
269 |
270 | There is of course nothing stopping you from running the script with an actual AHK EXE yourself on the command line. So if you use:
271 | ```
272 | Run("'X:\Path\to\AutoHotkey.exe' 'path\to\script.ahk'",,,&pid)
273 | ```
274 | ... then you will get the PID of the specified script as expected.
275 |
276 | ## To-Do List
277 |
278 | * Allow options to compile one script into multiple versions with minimal clicks (maybe).
279 |
280 | ## Other remarks...
281 |
282 | This program will NOT circumvent User Account Control settings. If you leave UAC enabled this program will still work as long as you keep the `Install For` option in the `Basics` tab set to "Current User".
283 |
284 | If you want to install for "All Users" (which affects the entire system) then you must run this program as admin. You can do this by right-clicking on the Launcher EXE (`AHK_Portable_Installer.exe`) and selecting "Run as administrator".
285 |
286 | If you want to completely disable UAC on Win 7+ you need to disable Admin Approval Mode as well. Note that this will have the effect of launching ALL scripts (and all programs on your system for that matter) as admin. And all scripts will have UI access as well. At this time, UI Access is not possible with AHK Portable Installer when UAC/Admin Approval Mode is enabled.
287 |
288 | Below is the registry key modifications needed to disable UAC and Admin Approval Mode in one registry key, but first open the User Account Control window and pull the slider all the way down. Then merge the below registry setting.
289 |
290 | ```
291 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]
292 | "EnableLUA"=dword:00000000
293 | ```
294 |
295 | If you don't have some of these registry keys/items you need to create them.
296 |
297 | You can also follow [this tutorial on TenForums](https://www.tenforums.com/tutorials/112488-enable-disable-user-account-control-uac-windows.html).
298 |
299 |
300 |
301 | ## *** WARNING ABOUT DISABLING UAC ***
302 | If you disable UAC, you will disable some of the built-in countermeasures in Windows that protect your system.
303 |
304 | Do not do this unless you are:
305 | 1) Able to administer your own system security (like a paid anti-virus subscription).
306 | 2) You are willing to accept the consequences.
307 |
308 | ---
309 |
310 | Any feedback would be appreciated. Hopefully this tool will help people, and just get better over time.
311 |
--------------------------------------------------------------------------------
/images/ahk-pi-main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/images/ahk-pi-main.png
--------------------------------------------------------------------------------
/inc/TheArkive_reg2.ahk:
--------------------------------------------------------------------------------
1 | ; ======================================================================================
2 | ; Reg class - wrapper for the REG command.
3 | ; ======================================================================================
4 | ; This was made to attempt making a wider set of functionality for interacting with the
5 | ; registry feel more "native" to AutoHotkey. Also, less errors are thrown, for example
6 | ; when trying to read the (Default) value of a key, you will get "" if the data of the
7 | ; (Default) value is a zero-length string, or is unset. But you can still check if the
8 | ; value is actually unset. Read more below.
9 | ;
10 | ; Much of this class mirrors what AHK already does with registry related commands and
11 | ; functions, and of course adds to it.
12 | ;
13 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
14 | ; Methods
15 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16 | ; reg.query(key, value := "", recurse := false)
17 | ;
18 | ; - Similar to the REG QUERY command.
19 | ;
20 | ; - If recurse = false, specify only a key to query all values, as well as a list
21 | ; of keys (and their default values) in the specified key. If you also specifify
22 | ; a value, then only that value's data is returned.
23 | ;
24 | ; - If recurse = true, specify only a key to query all values in all subkeys. If
25 | ; you also specify a value, then all instaces of that value in all subkeys is
26 | ; queried.
27 | ;
28 | ; - Output is a Map(), listing the key queried, and all subkeys.
29 | ;
30 | ; - If value = "", that queries the (Default) value of a key/subkey.
31 | ;
32 | ; Usage Examples:
33 | ;
34 | ; string := reg.query("HKCU\My_key", "my_value")
35 | ;
36 | ; -> Returns the data stored in "my_value" as a string in the same manner as RegRead().
37 | ;
38 | ; assoc_array := reg.query("HKCU\My_key",,true)
39 | ;
40 | ; -> Returns a Map() of all values and sub-keys.
41 | ;
42 | ; assoc_array := reg.query("HKCU\My_key","My_value",true)
43 | ;
44 | ; -> Returns a Map() of all instances of "My_value" in the specified key and subkeys.
45 | ;
46 | ; + Getting data from an array after using reg.query()
47 | ;
48 | ; my_data := arr["HKLM\sub_key"]["value"]["data"]
49 | ; my_data_type := arr["HKLM\sub_key"]["value"]["data_type"]
50 | ; (boolean) check_unset := arr["HKLM\sub_key"]["value"]["unset"] ; only true when (Default) value is unset.
51 | ;
52 | ; - ["unset"] will indicate if the return value of "" is zero-length or actually unset.
53 | ;
54 | ; reg.add(key, value := "", data := "", rgType := "REG_SZ")
55 | ;
56 | ; - Specify only a key, to add a registry key with the (Default) value unset.
57 | ;
58 | ; - Specify a key and value, to write data to that value.
59 | ;
60 | ; - The default data type is REG_SZ.
61 | ;
62 | ; Usage Examples:
63 | ;
64 | ; boolean := reg.add("HKCU\Software\my_key")
65 | ;
66 | ; -> Attempts to add "my_key" subkey to the registry key "HKCU\Software".
67 | ;
68 | ; boolean := reg.add("HKCU\Software\my_key","my_value")
69 | ;
70 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key"
71 | ; with no data (a zero-length string).
72 | ;
73 | ; boolean := reg.add("HKCU\Software\my_key","my_value","some_data")
74 | ;
75 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key"
76 | ; with the string "some_data" as the string value.
77 | ;
78 | ; boolean := reg.add("HKCU\Software\my_key","my_value","some_data`r`n more_data","REG_MULTI_SZ")
79 | ;
80 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key" with the
81 | ; string "some_data" as the data. The data type "REG_MULTI_SZ" is specified.
82 | ; The default delimiter for REG_MULTI_SZ input is "`n" or "`r`n".
83 | ;
84 | ; reg.delete(key, value := "", clearKey := false)
85 | ;
86 | ; - Specify only a key to delete that key.
87 | ;
88 | ; - Specify a key and value to delete the value.
89 | ;
90 | ; - If clearKey = true, then the specified key's values are cleared. All subkeys and
91 | ; values in those subkeys are not touched. If value is specified in this case, it
92 | ; is ignored.
93 | ;
94 | ; boolean := reg.delete("HKCU\Software\my_key")
95 | ;
96 | ; -> Deletes registry key "HKCU\Software\my_key".
97 | ;
98 | ; boolean := reg.delete("HKCU\Software\my_key",,true)
99 | ;
100 | ; -> Deletes all values within the key "HKCU\Software\my_key". All subkeys
101 | ; and values within subkeys are left intact, as well as the specified key.
102 | ;
103 | ; boolean := reg.delete("HKCU\Software\my_key","my_value")
104 | ;
105 | ; -> Delete value named "my_value" from registry key "HKCU\Software\my_key".
106 | ;
107 | ; reg.export(key, file := "", overwrite := true)
108 | ;
109 | ; - Exports the specified key to a .reg file. If no file name is specified, then the name
110 | ; of the specified subkey is used as the file name. This may fail if the specified subkey
111 | ; contains characters that are not valid for file names. The default action is to overwrite
112 | ; the .reg file if it exists in the destination directory.
113 | ;
114 | ; reg.import(file)
115 | ;
116 | ; - Imports the specified .reg file.
117 | ;
118 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119 | ; Properties
120 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
121 | ; reg.view
122 | ;
123 | ; - Default value = ""
124 | ; - Other values = 32 or 64
125 | ; - Specifies whether to look at the 32 or 64 bit side of the registry. Changing
126 | ; this value to 32 or 64 will invoke SetRegView. Reading this value will return
127 | ; the current contents of A_RegView, however if the current reg view is "Default"
128 | ; then the returned value will be "".
129 | ; - If the user attempts to apply any other value besides 32, 64, or "", then ""
130 | ; is automatically applied (which is the same as "Default" for SetRegView).
131 | ;
132 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
133 | ; The .reason, .LastError, and .cmd properties are reset when a method() is called.
134 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
135 | ; reg.reason
136 | ;
137 | ; - Contains text describing error message. On no error, this value is blank.
138 | ;
139 | ; reg.LastError
140 | ;
141 | ; - Contains A_LastError code, or the most appropriate equivalent value. A value of
142 | ; zero indicates success.
143 | ;
144 | ; reg.cmd
145 | ;
146 | ; - Contains the full command line passed to RunWait() in case you want to double check it.
147 | ;
148 | ; Usage Examples:
149 | ;
150 | ; cur_reg_view := reg.view ; gets the current reg view
151 | ;
152 | ; reg.view := new_regView ; sets the new reg view (32, 64, or "")
153 | ;
154 | ; error_reason := reg.reason ; gets error message, when reg.method() returns false.
155 | ;
156 | ; err_code := reg.LastError
157 | ;
158 | ; ======================================================================================
159 | ; EXAMPLES
160 | ; ======================================================================================
161 | ;
162 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799
163 | ; test := jxon_dump(reg.query("HKCR\*","AttributeMask",true),4) ; gets all data from values named "AttributeMask" in HKCR\* (recursive)
164 | ; A_Clipboard := test
165 | ; msgbox test
166 | ;
167 | ; ======================================================================================
168 | ;
169 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799
170 | ; test := jxon_dump(reg.query("HKCR\*","",true),4) ; gets all (Default) values from all subkeys (recursive)
171 | ; A_Clipboard := test
172 | ; msgbox test
173 | ;
174 | ; ======================================================================================
175 | ;
176 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799
177 | ; test := jxon_dump(reg.query("HKCR\*",""),4) ; gets all (Default) values from specified key and 1st level subkeys (not recursive)
178 | ; A_Clipboard := test
179 | ; msgbox test
180 | ;
181 | ; ======================================================================================
182 | ;
183 | ; output_array := reg.query("HKEY_CLASSES_ROOT\*") ; looping through array returned by reg.query()
184 | ; msg_txt := ""
185 | ; For key, key_contents in output_array { ; looping through reg.query() output in a FOR loop
186 | ; msg_txt .= key "`r`n"
187 | ; For value, val_info in key_contents {
188 | ; msg_txt .= " VALUE: " value " (TYPE: " val_info["type"] ")`r`n"
189 | ; msg_txt .= " DATA: " (val_info["unset"] ? "(value not set)" : val_info["data"]) "`r`n"
190 | ; }
191 | ; }
192 | ; msgbox msg_txt
193 | ;
194 | ; ======================================================================================
195 | ;
196 | ; test := reg.read("HKCU\*","") ; reads default value of key "HKCR\*" and doesn't throw an error
197 | ; msgbox "default value: " test "`r`nunset? " (reg.unset ? "yes" : "no") ; check reg.unset for determining if a (Default) value is unset
198 | ;
199 | ; ======================================================================================
200 | ;
201 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2") ; adds only a key
202 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2","test3") ; adds a blank value named "test3"
203 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2","test3","value data") ; adds value "test3" set to "value data"
204 | ;
205 | ; ======================================================================================
206 | ;
207 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","","test") ; add a simple test key with (Default) value data set
208 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","test_value","data for test value") ; add test value
209 | ; Msgbox "test key, test value, and (Default) data set"
210 | ; reg.delete("HKCU\Software\AutoHotkey\test1\test2\test3","") ; clears default value of test key
211 | ; msgbox "default value of test key cleared"
212 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","test_value") ; overwrite test_value with zero-length string
213 | ; msgbox "overwrites test value with zero-length string"
214 | ; reg.delete("HKCU\Software\AutoHotkey\test1\test2\test3","test_value") ; removes test_value
215 | ; msgbox "test value removed"
216 | ; reg.delete("HKCU\Software\AutoHotkey\test1") ; remove the key
217 | ; msgbox "test key removed"
218 | ;
219 | ; ======================================================================================
220 | ;
221 | ; msgbox "adding dummy test values"
222 | ; reg.add("HKCU\Software\AutoHotkey","","test")
223 | ; reg.add("HKCU\Software\AutoHotkey","poof1","test1")
224 | ; reg.add("HKCU\Software\AutoHotkey","poof2","test2")
225 | ; msgbox "now .delete() and set clearKey = true"
226 | ; reg.delete("HKCU\Software\AutoHotkey",,true)
227 | ; msgbox "now refresh the registry/RegEdit. All test values should be cleared."
228 | ;
229 | ; ======================================================================================
230 | ;
231 | ; msgbox "WARNING: Please move through this example SLOWLY."
232 | ; msgbox "EXPORTING`r`n`r`nzero means success: " reg.export("HKCU\Software\AutoHotkey","HKCU-Ahk-registry.reg") ; exports HKCR\* to a .reg file
233 | ; msgbox "reg hive exported`r`n`r`nCheck exported file contents if you wish.`r`n`r`nRename AutoHotkey in HKCU\Software to AutoHotkey2 now for this test"
234 | ; msgbox "IMPORTING`r`n`r`nzero means success: " reg.import("HKCU-Ahk-registry.reg") "`r`n`r`nManuall refresh registry/RegEdit now (F5)."
235 | ; msgbox "registry import done`r`n`r`nIt is safe to delete AutoHotkey2 key now."
236 | ; FileDelete "HKCU-Ahk-registry.reg"
237 | ; msgbox "test reg file deleted"
238 | ;
239 | ; ======================================================================================
240 | ; Examples testing error messages.
241 | ; ======================================================================================
242 | ;
243 | ; msgbox reg.read("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","") ; should be restricted
244 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError
245 | ;
246 | ; msgbox reg.export("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","test.reg") ; should be restricted
247 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError
248 | ;
249 | ; msgbox reg.add("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","","test data") ; should be restricted
250 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError
251 | ;
252 | ; msgbox reg.delete("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","") ; should be restricted
253 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError
254 | ;
255 | ; ======================================================================================
256 | ; The following .reg file contents should fail, this key should be restricted.
257 | ; Copy below contents to a error_test.reg file and place it in the same folder as
258 | ; this script. Attempt to import with commands below to see error codes/messages.
259 | ; If for some reason you have access to this key, it is recommended you remove the
260 | ; default value from the specified key below.
261 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
262 | ; Windows Registry Editor Version 5.00
263 | ;
264 | ; [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers]
265 | ; @="testing"
266 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
267 | ; msgbox reg.import("error_test.reg")
268 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError
269 | ; RegDelete "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers" ; ONLY run this if for some reason the above commands
270 | ; ; actually write to this key's default value.
271 | ;
272 | ; ======================================================================================
273 | ;
274 | ; msgbox "result: " reg.export("HKCU\Software\AutoHotkey","overwrite_test.reg") ; overwrite test
275 | ; msgbox "should fail: " reg.export("HKCU\Software\AutoHotkey","overwrite_test.reg",false) ; should fail
276 | ; FileDelete "overwrite_test.reg" ; clean up test file
277 | ;
278 | ; ======================================================================================
279 |
280 | class reg {
281 | Static null := Chr(3), cmd := "", unset := false, reason := "", LastError := 0, lastKey := ""
282 |
283 | Static query(key, value := "", recurse := false) { ; REG QUERY simulated
284 | this.lastKey := key
285 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := ""
286 | If (this.access_test(key)) ; test for access denied - this is very appropriate here
287 | return this.LastError
288 |
289 | output := Map(), output.CaseSense := false, readValue := true ; init base array
290 | If (value = "") { ; only append first default value if (value = "")
291 | d := Map(), d.CaseSense := false, d["data"] := "", d["type"] := "REG_SZ" ; init default value for base key
292 | Try d["data"] := RegRead(key,"") ; try to look up first default key
293 | d["unset"] := (A_LastError = 2) ? true : false ; check if first default value is unset
294 | output[this.ExpandRoot(key)] := Map("(Default)",d) ; write first default value
295 | }
296 |
297 | Loop Reg key, (recurse ? "VKR" : "VK")
298 | {
299 | k := A_LoopRegKey, v := A_LoopRegName, t := A_LoopRegType, m := A_LoopRegTimeModified
300 | (t = "KEY") ? (k := k "\" v, v := "", t := "REG_SZ") : ""
301 | readValue := (value = this.null) ? true : (value = v) ? true : false ; read value based on params/filters
302 |
303 | If (readValue) {
304 | If !output.Has(k)
305 | n := Map(), n.CaseSense := false, output[k] := n
306 | d := Map(), d.CaseSense := false, d["data"] := "", d["type"] := t
307 | Try d["data"] := RegRead(k,v)
308 | d["unset"] := (A_LastError = 2) ? true : false
309 | output[k][!v ? "(Default)" : v] := d
310 | }
311 | }
312 | this.LastError := 0, this.reason := ""
313 | return output
314 | }
315 | Static read(key, value := "") {
316 | this.lastKey := key
317 | result := "", this.cmd := ""
318 | Try result := RegRead(key,value)
319 | this.LastError := A_LastError
320 | this.reason := this.validate_error(A_LastError)
321 | this.unset := (this.LastError = 2) ? true : false
322 | return result
323 | }
324 | Static add(key, value := "", data := "", rgType := "REG_SZ") {
325 | this.lastKey := key
326 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := ""
327 | If (value = this.null And data = "") { ; write a blank vlaue
328 | Try RegWrite data, rgType, key
329 | If !A_LastError
330 | Try RegDelete key
331 | } Else ; write a value
332 | Try RegWrite data, rgType, key, value
333 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError
334 | return this.LastError
335 | }
336 | Static delete(key, value := "", clearKey := false) {
337 | this.lastKey := key
338 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "", curValue := ""
339 | If (value = this.null And !clearKey) {
340 | Try RegDeleteKey key
341 | } Else If (value = this.null And clearKey) {
342 | Loop Reg key
343 | {
344 | curValue := A_LoopRegName
345 | Try RegDelete A_LoopRegKey, A_LoopRegName
346 | If A_LastError
347 | Break
348 | }
349 | If !A_LastError
350 | curValue := ""
351 | } Else
352 | Try RegDelete key, value
353 |
354 | this.reason := this.validate_error(A_LastError) (curValue ? "`r`n`r`n Key: " key "`r`nValue: " curValue : "")
355 | this.LastError := A_LastError
356 | return this.LastError
357 | }
358 | Static export(key, file := "", overwrite := true) {
359 | this.lastKey := key
360 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := ""
361 | If (this.access_test(key)) ; test for access denied
362 | return this.LastError
363 |
364 | SplitPath key, &endKey, &pathKey
365 | file := (file="") ? endKey ".reg" : file ; default file name is key name
366 | file := (SubStr(file,-4) != ".reg") ? file ".reg" : file
367 | v := this.validateFileName(file) ; validate provided file/path
368 |
369 | If (!v) {
370 | this.reason := "Invalid Filename", this.LastError := 1
371 | } Else If (!overwrite And FileExist(file))
372 | this.reason := "File exists, no overwrite", this.LastError := 1
373 | Else {
374 | r := (this.view = 32) ? " /reg:32" : (this.view = 64) ? " /reg:64" : ""
375 | o := (overwrite ? " /y" : "") ; check for overwrite, adjust command line
376 | this.cmd := "reg export " key " " Chr(34) file Chr(34) o r
377 | result := RunWait(this.cmd)
378 | If A_LastError
379 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError
380 | Else If result
381 | this.reason := "Key may not exist", this.LastError := 1
382 | }
383 | return this.LastError
384 | }
385 | Static import(file, key := "") { ; specify key if you want to test for access first
386 | this.lastKey := key
387 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := ""
388 | If (key And this.access_test(key)) ; test for access denied
389 | return this.LastError
390 |
391 | v := this.validateFileName(file) ; validate provided file/path
392 | r := (this.view = 32) ? " /reg:32" : (this.view = 64) ? " /reg:64" : ""
393 |
394 | If !v
395 | this.LastError := 1, this.reason := "Invalid file name."
396 | if FileExist(file) {
397 | this.cmd := "reg import " Chr(34) file Chr(34) r
398 | result := RunWait(this.cmd)
399 | If A_LastError
400 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError
401 | Else If result
402 | this.reason := "Key may not exist, or access is denied", this.LastError := 1
403 | } Else
404 | this.LastError := 1, this.reason := "File does not exist."
405 |
406 | return this.LastError
407 | }
408 | Static access_test(key) {
409 | this.lastKey := key
410 | Try test := RegRead(key,"")
411 | If (A_LastError = 5) { ; test for access rights / access denied
412 | this.LastError := A_LastError, this.reason := this.validate_error(A_LastError)
413 | return this.LastError
414 | } Else
415 | return 0
416 | }
417 | Static validate_error(errNum) {
418 | If (errNum = 1)
419 | result := "General failure. Key/value/file may not exist, or may not be invalid."
420 | Else If (errNum = 2)
421 | result := "File not found"
422 | Else if (errNum = 5)
423 | result := "Access Denied"
424 | Else
425 | result := errNum
426 |
427 | return result
428 | }
429 | Static validateFileName(inPath) {
430 | If !inPath
431 | return false
432 | SplitPath inPath, &outFile, &outDir
433 | result := (outDir ? FileExist(outDir) : true), invalidChars := "><:/\|?*" Chr(34)
434 | Loop Parse invalidChars
435 | result := !result ? false : !InStr(outFile, A_LoopField)
436 | return result
437 | }
438 | Static ExpandRoot(key) {
439 | key := RegExReplace(key,"i)^HKCR\\","HKEY_CLASSES_ROOT\",,1) , key := RegExReplace(key,"i)^HKCU\\","HKEY_CURRENT_USER\",,1)
440 | key := RegExReplace(key,"i)^HKLM\\","HKEY_LOCAL_MACHINE\",,1), key := RegExReplace(key,"i)^HKU\\" ,"HKEY_USERS\",,1)
441 | key := RegExReplace(key,"i)^HKCC\\","HKEY_CURRENT_CONFIG",,1), key := RegExReplace(key,"i)^HKPD\\","HKEY_PERFORMANCE_DATA",,1)
442 | return key
443 | }
444 | Static CollapseRoot(key) {
445 | key := RegExReplace(key,"i)^HKEY_CLASSES_ROOT\\","HKCR\",,1) , key := RegExReplace(key,"i)^HKEY_CURRENT_USER\\","HKCU\",,1)
446 | key := RegExReplace(key,"i)^HKEY_LOCAL_MACHINE\\","HKLM\",,1) , key := RegExReplace(key,"i)^HKEY_USERS\\","HKU\",,1)
447 | key := RegExReplace(key,"i)^HKEY_CURRENT_CONFIG\\","HKCC\",,1), key := RegExReplace(key,"i)^HKEY_PERFORMANCE_DATA\\","HKPD\",,1)
448 | return key
449 | }
450 | Static view { ; handle reg view natively with AHK
451 | Set {
452 | SetRegView ((value = 32 Or value = 64) ? value : "Default" )
453 | }
454 | Get {
455 | return ((A_RegView = "Default") ? "" : A_RegView)
456 | }
457 | }
458 | }
--------------------------------------------------------------------------------
/inc/_JXON.ahk:
--------------------------------------------------------------------------------
1 | ;;;; AHK v2
2 | ; Example ===================================================================================
3 | ; ===========================================================================================
4 |
5 | ; Msgbox "The idea here is to create several nested arrays, save to text with jxon_dump(), and then reload the array with jxon_load(). The resulting array should be the same.`r`n`r`nThis is what this example shows."
6 | ; a := Map(), b := Map(), c := Map(), d := Map(), e := Map(), f := Map() ; Object() is more technically correct than {} but both will work.
7 |
8 | ; d["g"] := 1, d["h"] := 2, d["i"] := ["purple","pink","pippy red"]
9 | ; e["g"] := 1, e["h"] := 2, e["i"] := Map("1","test1","2","test2","3","test3")
10 | ; f["g"] := 1, f["h"] := 2, f["i"] := [1,2,Map("a",1.0009,"b",2.0003,"c",3.0001)]
11 |
12 | ; a["test1"] := "test11", a["d"] := d
13 | ; b["test3"] := "test33", b["e"] := e
14 | ; c["test5"] := "test55", c["f"] := f
15 |
16 | ; myObj := Map()
17 | ; myObj["a"] := a, myObj["b"] := b, myObj["c"] := c, myObj["test7"] := "test77", myObj["test8"] := "test88"
18 |
19 | ; g := ["blue","green","red"], myObj["h"] := g ; add linear array for testing
20 |
21 | ; q := Chr(34)
22 | ; textData2 := Jxon_dump(myObj,4) ; ===> convert array to JSON
23 | ; msgbox "JSON output text:`r`n===========================================`r`n(Should match second output.)`r`n`r`n" textData2
24 |
25 | ; newObj := Jxon_load(&textData2) ; ===> convert json back to array
26 |
27 | ; textData3 := Jxon_dump(newObj,4) ; ===> break down array into 2D layout again, should be identical
28 | ; msgbox "Second output text:`r`n===========================================`r`n(should be identical to first output)`r`n`r`n" textData3
29 |
30 | ; ExitApp
31 |
32 | ; ===========================================================================================
33 | ; End Example ; =============================================================================
34 | ; ===========================================================================================
35 |
36 | ; originally posted by user coco on AutoHotkey.com
37 | ; https://github.com/cocobelgica/AutoHotkey-JSON
38 |
39 | Jxon_Load(&src, args*) {
40 | static q := Chr(34)
41 |
42 | key := "", is_key := false
43 | stack := [ tree := [] ]
44 | is_arr := Map(tree, 1) ; ahk v2
45 | next := q "{[01234567890-tfn"
46 | pos := 0
47 |
48 | while ( (ch := SubStr(src, ++pos, 1)) != "" ) {
49 | if InStr(" `t`n`r", ch)
50 | continue
51 | if !InStr(next, ch, true) {
52 | testArr := StrSplit(SubStr(src, 1, pos), "`n")
53 |
54 | ln := testArr.Length
55 | col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))
56 |
57 | msg := Format("{}: line {} col {} (char {})"
58 | , (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
59 | : (next == "'") ? "Unterminated string starting at"
60 | : (next == "\") ? "Invalid \escape"
61 | : (next == ":") ? "Expecting ':' delimiter"
62 | : (next == q) ? "Expecting object key enclosed in double quotes"
63 | : (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
64 | : (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
65 | : (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
66 | : [ "Expecting JSON value(string, number, [true, false, null], object or array)"
67 | , ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
68 | , ln, col, pos)
69 |
70 | throw Error(msg, -1, ch)
71 | }
72 |
73 | obj := stack[1]
74 | memType := Type(obj)
75 | is_array := (memType = "Array") ? 1 : 0
76 |
77 | if i := InStr("{[", ch) { ; start new object / map?
78 | val := (i = 1) ? Map() : Array() ; ahk v2
79 |
80 | is_array ? obj.Push(val) : obj[key] := val
81 | stack.InsertAt(1,val)
82 |
83 | is_arr[val] := !(is_key := ch == "{")
84 | next := q (is_key ? "}" : "{[]0123456789-tfn")
85 | } else if InStr("}]", ch) {
86 | stack.RemoveAt(1)
87 | next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
88 | } else if InStr(",:", ch) {
89 | is_key := (!is_array && ch == ",")
90 | next := is_key ? q : q "{[0123456789-tfn"
91 | } else { ; string | number | true | false | null
92 | if (ch == q) { ; string
93 | i := pos
94 | while i := InStr(src, q,, i+1) {
95 | val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
96 | if (SubStr(val, -1) != "\")
97 | break
98 | }
99 | if !i ? (pos--, next := "'") : 0
100 | continue
101 |
102 | pos := i ; update pos
103 |
104 | val := StrReplace(val, "\/", "/")
105 | val := StrReplace(val, "\" . q, q)
106 | , val := StrReplace(val, "\b", "`b")
107 | , val := StrReplace(val, "\f", "`f")
108 | , val := StrReplace(val, "\n", "`n")
109 | , val := StrReplace(val, "\r", "`r")
110 | , val := StrReplace(val, "\t", "`t")
111 |
112 | i := 0
113 | while i := InStr(val, "\",, i+1) {
114 | if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
115 | continue 2
116 |
117 | xxxx := Abs("0x" . SubStr(val, i+2, 4)) ; \uXXXX - JSON unicode escape sequence
118 | if (xxxx < 0x100)
119 | val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
120 | }
121 |
122 | if is_key {
123 | key := val, next := ":"
124 | continue
125 | }
126 | } else { ; number | true | false | null
127 | val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
128 |
129 | if IsInteger(val)
130 | val += 0
131 | else if IsFloat(val)
132 | val += 0
133 | else if (val == "true" || val == "false")
134 | val := (val == "true")
135 | else if (val == "null")
136 | val := ""
137 | else if is_key {
138 | pos--, next := "#"
139 | continue
140 | }
141 |
142 | pos += i-1
143 | }
144 |
145 | is_array ? obj.Push(val) : obj[key] := val
146 | next := obj == tree ? "" : is_array ? ",]" : ",}"
147 | }
148 | }
149 |
150 | return tree[1]
151 | }
152 |
153 | Jxon_Dump(obj, indent:="", lvl:=1) {
154 | static q := Chr(34)
155 |
156 | if IsObject(obj) {
157 | memType := Type(obj) ; Type.Call(obj)
158 | is_array := (memType = "Array") ? 1 : 0
159 |
160 | if (memType ? (memType != "Object" And memType != "Map" And memType != "Array") : (ObjGetCapacity(obj) == ""))
161 | throw Error("Object type not supported.`r`n`r`nObj Type: " Type(obj), -1, Format("