Table of Contents generated with DocToc
- Interactive HTA Documents - Powershell et, al
- Why?
- Implementation
- Requirements
- Features
- How to use
- Appendix
I needed a means of producing documents on the Windows platform that could better engage the reader and perform some advanced actions such as interacting with the system.
I used the boilerplate from this repo BlackrockDigital/startbootstrap-simple-sidebar to create the basis for the interactive document.
The layout is clean, and I managed to tweak it to my liking: check it out:
Here's how I accomplished my goal:
- markdown for easy content creation, see default.markdown
- pandoc for content rendering, and for creating the single, self-contained output
- markdown pre-processing with pp
- javascript ActiveX Objects for interaction with the Windows OS
powershell
code can be embedded directly in your markdown and/or templates, enclosed in html comment tags
<!-- -->
- The same goes for
cmd
commands (although not as elegantly)
- cmder for making the Windows commandline so much sweeter
- optional:
- python 2.7+ (only if you plan on installing the pandoc python module, i.e.
pip install pandoc
) - cmder(the full version is best, as it ships with git-bash)
- ansible-taskrunner # Don't worry, we're just utilizing the
tasks
command inbash
mode, so noansible
needed!
- python 2.7+ (only if you plan on installing the pandoc python module, i.e.
- mandatory:
The file is completely self-contained, with assets embedded as base64 objects.
I've incorporated HTAConsole into the source, which makes it substantially easier to troubleshoot bugs.
Simply press F12 to display the console. I use this feature throughout the codebase for printing helpful information to the console.
e.g. console.log('some message')
I plan on extending overall functionality to allow for wider integration of programming languages.
If you're on Windows, you should be able to install the project requirements using the bootstrap.bat script.
This will install chocolatey
, pp
, and pandoc
on your system. It requires the 7z
command, so it'll install that as well.
You can invoke the build process in any of 3 ways:
All of these act as a pandoc/pp wrapper to produce a single .hta/.html file as per specification.
- Invoke the build script from commandline:
- From
cmder
/git-bash
:
- Using the
tasks
command mentioned above
tasks run -s _template/default.markdown -o default.hta -t _template/templates/default.html
- Using the supplied
build.sh
bash script:
./build.sh -s _template/default.markdown -o default.hta -t _template/templates/default.html
- Using the supplied powershell script:
./build.bat -s _template\\default.markdown -o default.hta -t _template\\templates\\default.html
- Using the
- Invoking any of the commands above with the
--dry
flag will display something similar to:
pp _template/default.markdown | pandoc -o 'default.hta' -c '_common/templates/default.css' -H '_common/templates/header.html' --template _template/templates/default.html --self-contained --standalone
- I'm also utilizing watchdog to monitor file changes and trigger a rebuild based on specified parameters
Simply invoke any of the commands above with the--watch --patterns
flags, e.g.
--watch --patterns '*.markdown,*.md,*.js,*.css,*.html'
- From
- Invoke the build script from the hta:
- Just click the
Rebuild
button located in the top navigation bar.
This will invoke the powershell build script so long as it is located in the same directory as the HTA
Press F5 to refresh the HTA application
- Just click the
- Invoke the same build script from Example1, with just one difference:
-o default.html
- Add some powershell to the default.markdown:
<a href="#" id="testing" class="powershell">CLICKME: PowerShell
<!--
Write-Host 'You executed PowerShell!'
&pause
-->
</a>
As illustrated, you must enclose your commands/includes in comment tags <!-- -->
Do the same, this time with a non-interactive shell session:
<a href="#" id="testing" class="powershell" data-interactive="0">CLICKME: POWERSHELL
<!--
'You executed powershell!'
-->
</a>
- Again, but using the
!include
macro:
<a href="#" id="include" class="powershell" data-interactive="0">CLICKME: PowerShell
<!--
!include(src/include.ps1)
-->
</a>
Once the HTA file is rebuilt, you can refresh the HTA application by pressing F5. Your changes should have been rendered.
Again, you can access the javascript console by pressing F12.
You can review the STDOUT of non-interactive powershell command.
- Add a link with class 'shell' to the default.markdown:
<a href="#" class="shell" data-shell="cmd">Start cmd!</a>
Once the HTA file is rebuilt, you can refresh the HTA application by pressing F5. Your changes should have been rendered.
The powershell invocation is done through a WScript.Shell ActiveX Object and clever Windows clipboard manipulation, so there is no need for intermediary scripts to handle the powershell code.
From shell.js:
/**
* Silently execute a powershell command.
*/
function powershell(command_string, interactive, pause){
start = 'start \"\"'
if (pause == 1){
pause = '&pause'
} else {
pause = ''
}
if (interactive == 0){
hidden = '-w hidden -nologo'
start = ''
} else {
hidden = ''
}
var clipboardData_orig = window.clipboardData.getData("Text");
window.clipboardData.setData("Text",command_string);
var clipboardData = window.clipboardData.getData("Text");
console.log("Powershell command is: " + clipboardData)
command = String.format("%comspec% /c {0} PowerShell -noprofile {1} -Command $commands=$(\"Set-ExecutionPolicy Bypass -Scope Process -Force;add-type -AssemblyName System.Windows.Forms;$clipboardData = [System.Windows.Forms.Clipboard]::GetText() -split '\\r\\n';[String]::Join( ';', $( ( $clipboardData ) ))\");Invoke-Expression $commands 2>&1;{2} | clip", start, hidden, pause);
console.log("Invoked Powershell command via: " + command)
WshShell.run(command,0,true);
console.log("STDOUT: " + window.clipboardData.getData("Text"));
setTimeout(function(t){
window.clipboardData.setData("Text",clipboardData_orig);
}, 2000);
return
}
The WshShell ActiveX Object is instantiated in default.html.
This is the jquery for handling hyperlinks with class 'powershell'
/**
* For all html links (a) with class 'powershell' invoke the powershell command encased in comment strings <!--POWERSHELLCODE-->
*/
$( "a.powershell" ).click(function() {
var command_string = ''
var test = this.innerHTML.match(/<!--[\s\S]*-->/)
if (test != null){
var regex = /<!--|-->/gi;
command_string = test[0].replace(regex,'')
}else {
command_string = heredoc(function () {/*
'You must specify powershell commands for this link by encasing these in comment strings'
'e.g.'
'<a href="#" class="powershell">'
'<!--'
'Hello World!'
'-->'
'</a>'
&pause
*/});
alert(command_string)
return
}
var pause=this.getAttribute('data-pause')
if (pause != 1){
pause = 0
}
var interactive=this.getAttribute('data-interactive')
if (interactive != 0){
interactive = 1
}
powershell(command_string, interactive, pause);
});
see ui.js:
Note the use of custom 'data-*' html attributes, keeping in compliance at least with HTML5.
If you encounter errors in your document(s), try building with the --no-aio/-a
flag, as with:
tasks run -s _template/default.markdown -o default.hta -t _template/templates/default.html --no-aio
This will invoke the pandoc
command without the --standalone
and --self-contained
flags, which results in a document that offers itself more willingly to inspection.
Happy debugging :)
- BlackrockDigital/startbootstrap-simple-sidebar: An off canvas sidebar navigation Bootstrap HTML template created by Start Bootstrap
- jeremyben/HTAConsole: HTA Console is a basic but practical Javascript Console Log to help debugging HTML Applications.
- stack overflow - Get String in YYYYMMDD format from JS date object?
- stack overflow - Javascript heredoc - Stack Overflow
- stack overflow - jquery - JavaScript sleep/wait before continuing - Stack Overflow
- All You Need to Know About the HTML5 Data Attribute
- stack overflow - JavaScript equivalent to printf/String.Format - Stack Overflow