Some time later, I was passing the time on StackOverflow seeing if I could rack up some more points for being helpful with some problem solving, and up came a question about GUIs for batch files. HTA was a solution - quite a good one in fact. But it's funny that it seems that MS haven't been too hot about spreading the word about HTA - I have to say that even with all the geek sites that I surf daily it's not something I recall ever seeing getting much of a mention.
An HTA is simply HTML, displayed by the guts of Window's IE engine via mshta.exe - allowing you to display anything a browser can normally show, collect input, and run code that can't normally be run within HTML scripts. For example, write to local files. This means an HTA can be a script with a GUI, or it can be used to create a GUI for a separate script - command batch or Python, etc ...
Good ol' Wikipedia will explain more about HTA (HTML Applications), and MS gives you the basics - so all I can do here is to present a full little example showing how simple it would be to add some spice to a script (Command .bat Batch, Python or other) that needs some user input. Here we can see how simple it is to display the HTML GUI from the script, passing in any required parameters if required, and retrieve the user's input afterwards.
Example HTAThe HTML below is all you need to produce a basic example GUI like this :
<head> <title>My HTA Test</title> <HTA:APPLICATION ID="oHTA" APPLICATIONNAME="HTA Test" SCROLL="no" SINGLEINSTANCE="yes" WINDOWSTATE="maximize" > </head> <SCRIPT LANGUAGE="VBScript"> Sub NoContextMenu() window.event.CancelBubble= True window.event.ReturnValue= False ' Alert("Right-click is not allowed, sorry!") End Sub
Sub Window_onLoad ' use the following line to disable right-click menu and prevent View Source Set document.onContextMenu=GetRef("NoContextMenu") ' resize to quarter of screen area, centered window.resizeTo screen.availWidth/2,screen.availHeight/2 window.moveTo screen.availWidth/4,screen.availHeight/4 'Msgbox oHTA.commandLine ' Passed-in code - will execute to set variables arrParams = Split(oHTA.commandLine, "`") For i = 1 to (Ubound(arrParams)) ' Msgbox arrParams(i) execute(arrParams(i)) Next ' SCRIPT-specific code : set controls from executed code txt_bp.value=batch_path txt_num1.value=num1 txt_str1.value=str1 End Sub Sub Set_Env_Vars ' msgbox "writing " & txt_str1.value Set oWSS = CreateObject("WScript.Shell") ' FYI: msgbox oWSS.ExpandEnvironmentStrings("%WinDir%") strBat="Echo @SET ""ZZ_NUM1=" & txt_num1.value _ & """ ^& @SET ""ZZ_STR1=" & txt_str1.value & """ > ""%TEMP%""\testhta-set.bat" strBat=strBat & " & Echo @SET ZZ_NUM1=^& @SET ZZ_STR1=^&> ""%TEMP%""\testhta-clr.bat" oWSS.run "cmd /C " & strBat ,0,true Set oWSS = Nothing self.close End Sub </SCRIPT> <BODY bgcolor="#ccccFF"> <h1>Hello!</h1> Just a little test.... <P> Batch Path <input type="text" value="" name="txt_bp""> <p> Number <input type="text" value="" name="txt_num1""> <p> String <input type="text" value="" name="txt_str1""> <p> <input type="button" value="Return these to the batch" name="run_button" onClick="Set_Env_Vars"> </BODY>
The <Head> section contains the HTA:APPLICATION tag to specify a number of options about the HTA.
In the <Script> section we have :
* Sub NoContextMenu()
(along with the first line of Window_onLoad)
Prevents the user from seeing the source code by disabling any right-click pop-up menu which would have had a View Source option. The user won't have any clues to reveal that it's an HTA.
* Sub Window_onLoad
Sets the window size and location, runs the code passed in from the batch, and then uses those newly set variables to set the control values.
* Sub Set_Env_Vars
This is called as the click action for the button, and uses a WScript.Shell object to run a single line of Command Line code (two or more commands can be executed in one line with a & between them) in order to create a temporary batch file in the %TEMP% folder. This - containing environment variable assignments - will be called from the main batch file after the HTA has closed, providing a mechanism to get the values available to the batch file.
Sure, you could write the file the hard way in the VBScript, but why not call a simple piece of familiar command line code instead - with all the %TEMP% decoding for free?
In the <Body> section, we have normal HTML with <Input> controls, and whatever ever else you would need to make things look beautiful.
Example Batch FileYou could, of course, call the HTA directly and rely upon it doing whatever it needs to do itself, or arrange for it to call hidden batch files to do any heavy lifting. But this blog post is about calling an HTA from an already-running batch file for user interaction.
This testhta.bat calls the above HTA, passing in some code that gets executed within the HTA - setting some variables. Everything after the backtick ` is interpreted as code by the HTA. Several lines of code can be compressed into one line by using a : between each command.
In this example the %~dp0 trick is passed in (shown in the GUI but not passed back to the batch) to show how you could pass in the batch's path in case you decide to try building in functions within the batch file (chunks of code that get bypassed in normal use but can be used by calling the batch with suitable parameter to enable a goto), to be called from within the HTA - another can of worms of complexity! (with the advantage of containing all your code in just two files, the .bat and .hta)
When control returns to the batch, it calls the temporary batch file that the HTA created, and in this way the cmd environment variables are set and will then be available to use within the batch. Here's an example of the "%TEMP%\testhta-set.bat" contents:
@SET "ZZ_NUM1=5.678" & @SET "ZZ_STR1=Oh, I see."
@echo off start /wait mshta.exe "C:\somepath\test.hta" ` batch_path="%~dp0" : num1=3.14 : str1="String Param" : msgbox("Wow!") if exist "%TEMP%\testhta-set.bat" call "%TEMP%\testhta-set.bat" echo Result: %ZZ_STR1% rem Tidy up and remove Env Vars & temp batches if exist "%TEMP%\testhta-clr.bat" call "%TEMP%\testhta-clr.bat" if exist "%TEMP%\testhta-clr.bat" del "%TEMP%\testhta-clr.bat" if exist "%TEMP%\testhta-set.bat" del "%TEMP%\testhta-set.bat"
Yes, I realise that including runable code - in the call to the HTA file - is something that would raise an eyebrow or two amongst purists, but this is just one simple way to pass in data to be used by the HTA - there are other ways. This is for personal use, or where security issues aren't a problem. Just showing the easiest way to get you started!
Python versionSome subtle differences here in testhta.py :
#Python v3.3 print("Hello, World!") # First, call the HTA - using | in place of any " - and wait for it to close from subprocess import Popen # now we can reference Popen from subprocess import PIPE new_process_obj = Popen(['mshta.exe', r'C:\your-path\testpy.hta', r'` batch_path=|no %~dp0 available| : num1=3.14 : str1=|String Param| : msgbox(|Wow!|)']) new_process_obj.wait() # retrieve a line of code to exec, from py file in temp folder process = Popen(['cmd.exe','/c type %TEMP%\\testhta.py & echo '], shell=False, stdin=PIPE,stdout=PIPE,stderr=None) output = process.stdout.readline() estr= output.decode("utf-8")[:-3] # delete some trailing "\r\n garbage from the & echo exec(estr) # now we have the variables set print(ZZ_NUM1) print(ZZ_STR1) # pause in case run from Win Explorer double-click input('Press <ENTER> to continue')
When the HTA is launched, the VBScript code is passed in with the | character used in place of ", for Pythonesque reasons. Also, when the string is retrieved within the HTA it will have an unwanted trailing " - which calls for two replaces :
For i = 1 to (Ubound(arrParams)) ' next 2 lines needed for Python method of calling the HTA arrParams(i)=Replace(arrParams(i),Chr(34),"") ' delete any " arrParams(i)=Replace(arrParams(i),"|",Chr(34)) ' any | becomes " execute(arrParams(i)) Next
One final difference is the method used to pass the values back to the Python script. This time I've used a testhta.py file in %TEMP% to be exec-uted. Here's an example, one line with two commands separated by a semi-colon :
ZZ_NUM1=3.14; ZZ_STR1="String Param"
This is created by this modified part of the orginal HTA (in Sub Set_Env_Vars) :
strBat="Echo ZZ_NUM1=" & txt_num1.value _ & "; ZZ_STR1=" & chr(34) & txt_str1.value & chr(34) & " > ""%TEMP%""\testhta.py"
Of course, as both the HTA and Python have access to the Clipboard, that could be used for passing data back or forth instead, as one of many alternatives.
PowerShellI don't want to duplicate someone else's fine work, so have a good read here :
In this case the author is starting from the HTA and launching the PowerShell from there, instead of the other way around. Whatever seems logical to you!