HTML Application Development Guide

Status: Work-in-progress
Last update: 07/09/2021
Source: MarkTiedemann/hta

Table of contents

1. Intro to HTAs

TODO
For more details, see the Wikipedia article or read "Introduction to HTML Applications (HTAs)" in the Microsoft documentation.

1.1. Why use HTAs

TODO

1.2. The hta:application element

TODO
For more details, see the Microsoft documentation.

2. Common tasks

2.1. Executing a HTA file from the command line

By default, HTA files are associated with mshta.exe:
C:\> assoc .hta
.hta=htafile

C:\> ftype htafile
htafile=C:\Windows\SysWOW64\mshta.exe "%1" (...) %*
Therefore, HTA files can be executed directly. For example: C:\> hello.hta

2.x. Executing a JavaScript one-liner

C:\> mshta "javascript: alert(screen.availWidth); close();"

2.x. Get the location of the HTA file

<script>
	alert(location.pathname);
	close();
</script>

2.x. Read command line arguments

The commandLine property of the application object returns a quoted path to the HTA file as well as additional command-line arguments.

For example, assuming the following HTA file args.hta:

<hta:application id="app" />
<ol></ol>
<script>
	window.onload = function () {
		var ol = document.getElementsByTagName("ol")[0];
		var args = app.commandLine.split(/\s+/);
		for (var i = 0; i < args.length; i++) {
			var li = document.createElement("li");
			li.innerText = args[i];
			ol.appendChild(li);
		}
	};
</script>
Executing the HTA as:
C:\dev> .\args.hta one two=three four
Would generate the following HTML:
  1. "C:\dev\args.hta"
  2. one
  3. two=three
  4. four

2.2. Determine user agent compatibility

By default, HTAs render in Internet Explorer 5 quirks mode.

To determine user compatibility, you may use the documentMode and compatMode properties of the document object:

Example HTA file:
<script>
	function compatMode() {
		switch (document.compatMode) {
			case "BackCompat": return "quirks mode";
			case "CSS1Compat": return "standards mode";
		}
	}

	// Should alert "IE5 in quirks mode"
	alert("IE" + document.documentMode + " in " + compatMode());
	close();
</script>

2.3. Select user agent compatibility

Add a meta tag to the head of your HTML file:
<meta http-equiv="x-ua-compatible" content="ie=9" />
TODO

2.4. Minimize the window

Add a HTML Help ActiveX control object to your page:
<object id="minimize" classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
	<param name="command" value="minimize" />
</object>
Click the object:
<button onclick="minimize.Click();">Minimize</button>

2.5. Maximize the window

Add an HTML Help ActiveX control object to your page:
<object id="maximize" classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
	<param name="command" value="maximize" />
</object>
Click the object:
<button onclick="maximize.Click();">Maximize</button>
Note that maximizing an already maximized window will restore its original size.

2.6. Close the window

<script>
	close();
	// Or alternatively:
	window.close();
	self.close();
</script>

2.7. Work with the file system

The FileSystemObject has a number of methods that allow you to manipulate files, folders, paths, and drives.

Another important object in this category is the TextStream object which is used for reading and writing files.

2.7.1. Write a file

<script>
	// Write "hello" into the file "world.txt"
	var fs = new ActiveXObject("Scripting.FileSystemObject");
	var stream = fs.createTextFile("world.txt", /*Overwrite*/ true);
	stream.write("hello");
	stream.close();
	close();
</script>

2.7.2. Read a file

<script>
	// Read "hello" from the file "world.txt" 
	var fs = new ActiveXObject("Scripting.FileSystemObject");
	var stream = fs.openTextFile("world.txt", /*ForReading*/ 1);
	alert(stream.readAll());
	stream.close();
	close();
</script>

2.8. Work with the shell

For more details, see the "Windows Script Host Basics" documentation by vbsedit.com, specifically the the WScript.Shell documentation.

2.8.1. Read an environment variable

To read an environment variable, use the ExpandEnvironmentStrings method of the WshShell object.

For example, the following code will expand the %windir% environment variable and print its value (C:\Windows, on most systems):

<script>
	var sh = new ActiveXObject("WScript.Shell");
	alert(sh.expandEnvironmentStrings("%windir%"));
	close();
</script>

2.8.2. Start an application

Start the Windows calculator:
<script>
	var sh = new ActiveXObject("WScript.Shell");
	sh.run("calc");
	close();
</script>

2.8.3. Start an application without a window

Start a PHP webserver in the background:
<script>
	var sh = new ActiveXObject("WScript.Shell");
	sh.run("cmd /c \"php -S localhost:8000\"", 0, false);
	close();
</script>

2.8.4. Execute an application and capture its output

Execute powershell and print its version:
<script>
	var sh = new ActiveXObject("WScript.Shell");
	var proc = sh.exec("powershell -c $PSVersionTable.PSVersion.toString()");
	alert(proc.stdout.readAll());
	close();
</script>

2.9. Work with the registry

The WshShell object also contains methods for working with the Windows Registry, specifically RegWrite, RegRead, and RegDelete:

2.9.1. Write a registry entry

<script>
	// Write "hello" into the registry entry HKCU\Software\world
	var sh = new ActiveXObject("WScript.Shell");
	sh.regWrite("HKCU\\Software\\world", "hello");
	close();
</script>

2.9.2. Read a registry entry

<script>
	// Read "hello" from the registry entry HKCU\Software\world
	var sh = new ActiveXObject("WScript.Shell");
	alert(sh.regRead("HKCU\\Software\\world"));
	close();
</script>

2.9.3. Delete a registry entry

<script>
	// Delete the registry entry HKCU\Software\world
	var sh = new ActiveXObject("WScript.Shell");
	sh.regDelete("HKCU\\Software\\world");
	close();
</script>

2.10. Work with a database

TODO

2.11. Execute a WMI query

In this example, we're going to query the Win32_Process class to get the Name and ProcessId of all running processes and display them in a table.
<h1>Processes</h1>
<table>
	<thead>
		<tr>
			<th>Name</th>
			<th>ProcessId</th>
		</tr>
	</thead>
	<tbody></tbody>
</table>
<script>
	window.onload = function () {
		var tbody = document.getElementsByTagName("tbody")[0];
		var locator = new ActiveXObject("WbemScripting.SWbemLocator");
		var service = locator.ConnectServer(".", "root\\cimv2");
		var collection = service.ExecQuery("SELECT Name, ProcessId FROM Win32_Process");
		var enumerator = new Enumerator(collection);
		while (!enumerator.atEnd()) {
			var item = enumerator.item();
			appendRow(tbody, item);
			enumerator.moveNext();
		}
	};
	function appendRow(tbody, item) {
		var tr = document.createElement("tr");
		var tdName = document.createElement("td");
		var tdProcessId = document.createElement("td");
		tdName.innerText = item.Name;
		tdProcessId.innerText = item.ProcessId;
		tr.appendChild(tdName);
		tr.appendChild(tdProcessId);
		tbody.appendChild(tr);
	}
</script>

3. HTAs for web developers

3.1. Log data (alert, console.log)

console.log does not work since console is undefined.

You can, however, use the alert function to quickly log information.

The following HTA file will open a message box with the content Hello, world!:

<script>
	alert("Hello, world!");
	close();
<script/>
Alternatively, you can use the LogEvent method of the WshShell object to log events to the Windows Event Log.
<script>
	var sh = new ActiveXObject("WScript.Shell");
	sh.logEvent(0, "This is a SUCCESS event");
	sh.logEvent(1, "This is an ERROR event");
	sh.logEvent(2, "This is a WARNING event");
	sh.logEvent(4, "This is an INFORMATION event");
	close();
</script>
To inspect the log, open the Event Viewer application (eventvwr.msc).

3.1. Store data (localStorage)

3.2.1. localStorage registry polyfill

var localStorage = (function () {
	var sh = new ActiveXObject("WScript.Shell");
	var root = "HKCU\\Software\\localStorage\\";
	return {
		getItem: function (key) {
			try {
				return sh.regRead(root + key);
			} catch (e) {
				return null;
			}
		},
		setItem: function (key, value) {
			sh.regWrite(root + key, value);
		},
		removeItem: function (key) {
			try {
				sh.regDelete(root + key);
			} catch (e) {}
		},
		clear: function () {
			try {
				sh.regDelete(root);
			} catch (e) {}
		}
	}
})();

3.2.2. localStorage file system polyfill

var localStorage = (function () {
	var fs = new ActiveXObject("Scripting.FileSystemObject");
	var sh = new ActiveXObject("WScript.Shell");
	var root = sh.ExpandEnvironmentStrings("%localappdata%") + "\\localStorage";
	return {
		getItem: function (key) {
			try {
				var file = fs.openTextFile(root + "\\" + key, 1);
				var value = file.readAll();
				file.close();
				return value;
			} catch (e) {
				return null;
			}
		},
		setItem: function (key, value) {
			if (!fs.folderExists(root)) {
				fs.createFolder(root);
			}
			var file = fs.createTextFile(root + "\\" + key, true);
			file.write(value);
			file.close();
		},
		removeItem: function (key) {
			try {
				fs.deleteFile(root + "\\" + key);
			} catch (e) {}
		},
		clear: function () {
			try {
				fs.deleteFolder(root);
			} catch (e) {}
		}
	}
})();

3.3. Fetch data (fetch, xhr)

TODO

3.4. Parse HTML

<script>
	var document = new ActiveXObject("HTMLFile");
	document.open();
	document.write("<h1 id=\"hello\">world</h1>");
	document.close();
	alert(document.getElementById("hello").innerText);
	close();
</script>

4. Miscellaneous

4.1. Embed the icon

Declare an embedded icon:
<hta:application icon="#" />
Prepend the icon to your HTA file:
C:\> copy /b icon.ico+app.hta app_with_embedded_icon.hta

4.2. TypeScript type declarations

TODO