Using a HTA as a PowerShell GUI

Over the past few months I have started using PowerShell as my primary scripting tool. Depending on what I am trying to achieve I sometimes like to have some form of GUI for my scripts, especially when user input is required.

Introduction to HTML Applications (HTA)

Previously I have always used HTML Applications (HTA) as my GUI and vbScript as the scripting language. HTAs are brilliant as they are simply HTML. And because a HTA is based on HTML, you can customise its look and feel by using the standard HTML styling properties.

In a nutshell, a HTA is exactly the same as a standard HTML page except for these two difference:

  • The extension for the file is .hta (and not .htm or .html)
  • The section includes the tag

An empty HTA “shell” (or template) looks like this:



  
    PowerShell HTA Example
    
    
  
  
  
  

As an overview, a HTA allows you to run client-side scripts that are included in HTML pages outside of the Internet Explorer’s security model.

Some bad news

Now for the bad news….PowerShell cannot be used directly within a HTA as it isn’t a client-side script. When I first found this out I was pretty devistated as I wanted to replace all my vbScripting with PowerShell.

But after some research and testing I got it – use vbScript to call the PowerShell script. Bingo, problem solved! Except for the little issue of passing data between the two environments (but we will get to that later on)…

Using vbScript to call a PowerShell script

First off, let’s have a look at how to call a PowerShell script from vbScript:


sCmd = "powershell.exe C:\Temp\PowerShell_HTA_Example.ps1"
Set oShell = CreateObject("Wscript.Shell")
iResult = oShell.Run(sCmd, 0, true)

As you can see above, we just create a Wscript.Shell object and then use the Run method to execute the command we stored in the sCmd variable. Let’s break down the oShell.Run line to see what each parameter does:

  • sCmd – command that you want to run (in this case powershell.exe and the path of the script we want to run).
  • 0 – is used to specify the appearance of the PowerShell window. In this case 0 sets it to hidden.
  • True – this specifies to wait for the command to completed before continuing with the execution of the script.

The only other thing to note here is that iResult holds the exit code of the PowerShell script. This is really handy because it allows for a neat way to handle errors and unexpected events in your PowerShell script by simply returning different values of exit codes. In my scripts I use the following standard:

  • Exit Code 0 – PowerShell script completed successfully
  • Exit Code 1 – PowerShell script completed successfully but with a warning (usually a non-terminating error)
  • Exit Code 2 – PowerShell script raised a terminating error and has exited pre-maturely

The Example

Now that we have gone through the foundations, lets get into the example. I will first post all of the code and then go through it section by section.

In the example, a HTA accepts a user input and passes it to PowerShell. PowerShell adds some text to the end of the user’s input and then depending on what the user has inputted will return different exit codes which will result in either a Success, Warning or Error message being displayed.

This is what the HTA looks like:

Using HTML Applications as a PowerShell GUI - HTA Example

File 1: HTA with vbScript



  
    PowerShell HTA Example
        
    
    
    
  
  
  
    

PowerShell HTA Example:

Enter some text to pass to the PowerShell script:
  • If you enter Warning you will return a warning result.
  • If you enter Error you will return an error result.
  • If you enter anything else you will get a success result.




Results:

File 2: PowerShell script


Param([Parameter(Mandatory=$true)][string]$Message)

#Set Error Action to Silently Continue
$ErrorActionPreference = "SilentlyContinue"

#If user has entered "Warning" then return Warning result.
#If user has entered "Error" then return error result.
#If user has entered anything else then return success result
switch ($Message){
  "Warning" {$ExitCode = 1; $sMessageProcessed = "$Message - This has been PowerShell processed"}
  "Error" {$ExitCode = 2; $sMessageProcessed = "$Message - This has been PowerShell processed"}
  default {$ExitCode = 0; $sMessageProcessed = "$Message - This has been PowerShell processed"}
}

#Copy new message to clipboard
$sMessageProcessed | clip

#Exit script returning the appropriate value
[Environment]::Exit(“$ExitCode”)

If you want to download the example files then they are available below. Note: You will need to change the PowerShell script path (line 25 in the HTA file) depending on where you save the files.

Step 1: Collect user input via vbScript

Ok hopefully now you are a little more familiar with the example above, which will make explaining a little easier.

Collecting the user’s input is easy – we just reference the HTML element’s id (which has to be unique) and assign it to a vbScript variable – in this case sMessage. Next we need to ensure that the user has actually inputted something and not done a sneaky. This again is just standard vbScript stuff… have a look below:


'Collect value from input form
sMessage = document.getElementByID("input_id").Value
      
'Check user has inputed data
If sMessage = "" Then
  MsgBox "Please enter something in the input form"
  Exit Sub
End If

Step 2: Pass user input from HTA to PowerShell

Now that we have got the user’s input in a vbScript variable we need to pass that to PowerShell. The easiest way to do this is when calling the PowerShell script by passing it as an argument.

To do this, you need to ensure you have specified that an argument be required in PowerShell. This is done in the first line:


Param([Parameter(Mandatory=$true)][string]$Message)

Not only does this line require an argument of “Message” to be passed when calling the script but it also is used to assign the passed data to a PowerShell variable – $Message.

Now for the vbScript side of things – passing the variable as an argument when calling the PowerShell script:


'Set command to call PowerShell script
sCmd = "powershell.exe C:\Temp\PowerShell_HTA_Example.ps1 -Message " & Chr(39) & sMessage & Chr(39)

Step 3: Processing the input

So at this moment the PowerShell script has been called and it has the user’s input stored within the $Message variable. For this example we are just going to do something really simple and check what the user has inputted and based on that return a different exit code to vbScript. Again the standard in my scripts is, exit code 0 = success, 1 = warning, 2 = error.


switch ($Message){
  "Warning" {$ExitCode = 1; $sMessageProcessed = "$Message - This has been PowerShell processed"}
  "Error" {$ExitCode = 2; $sMessageProcessed = "$Message - This has been PowerShell processed"}
  default {$ExitCode = 0; $sMessageProcessed = "$Message - This has been PowerShell processed"}
}

Step 4: Return output to HTA

Now that we have processed the the data we have an output we want to display to the user. In this PowerShell script the output is stored in the $MessageProcessed variable.

We now need to go in reverse – pass the output from PowerShell to vbScript to be then displayed in a HTML element within the HTA.

There are a number of different ways we can do this, but for small outputs it is easy to just use the clipboard. For larger and more complicated outputs you could output to a text file and then get vbScript to read that file. In this example we use clipboard, but I will show you both ways:

To clipboard


$sMessageProcessed | clip

To text file


$sMessageProcessed | Set-Content "C:\PowerShellOutput.txt"

The data is now “out” of PowerShell but we still need to collect it in vbScript so that we can display it to the user. This would obviously occur once the PowerShell script has finished executing, but I am showing you the vbScript code now so that it is easier to understand.

From clipboard


sResultData = window.clipboarddata.getdata("Text")

From text file


Set oFile = oFSO.OpenTextFile(sResultsTxtFile, 1)
Do Until oFile.AtEndOfStream
  sResultData = sResultData & oFile.ReadLine & "
" Loop oFile.Close

Step 5: Exit the PowerShell script returning an exit code

Now that the output has been copied to the clipboard we can exit the PowerShell script and return a exit code to vbScript. Depending on the exit code returned, vbScript will interact with the user differently.

In this case we are simply just just changing the font colour and message that is displayed to the user, however in a real life circumstance you would either show an error or success message depending on the exit code returned.

To exit PowerShell with a custom exit code use the following code:


[Environment]::Exit(“$ExitCode”)

Now vbScript needs to collect this exit code and place it in a variable for further processing. This has actually already been done – when we called the PowerShell script….


iResult = oShell.Run(sCmd, 0, true)

Here we can see that the “result” of the oShell.Run method will be stored in the iResult variable… and the result of the oShell.Run method is the PowerShell exit code. Cool ay 🙂

Step 6: Process exit code and display result to user

Now the rest is easy…. depending on the value of the exit code, display a different font colour and message to the user.


If iResult = 1 Then
  'Warning result
  document.getElementByID("result_id").innerHTML = "Warning Result: " & sResultData
ElseIf iResult = 2 Then
  'Error result
  document.getElementByID("result_id").innerHTML = "Error Result: " & sResultData
Else
  'Success result
  document.getElementByID("result_id").innerHTML = "Success Result: " & sResultData
End If

Once we have finalised our output all we need to do is display it to the user. This is easily done by the following line:


document.getElementByID("result_id").innerHTML = "Output goes here"

And that is it… you should now have a fully functioning HTA as a PowerShell GUI from where you can collect input and display the output to the user.

I really hope this helps you guys out and that some awesome scripts and utilities get developed from this… go wild!

If you have any questions, queries or more info – let me know in the comments below.

Thanks
Luca

Comments

  1. I am MOST grateful for the tutorial you’ve assembled. I am very much enlightened by the information and examples you’ve presented. However, I am a stone cold beginner and am working through this only because there’s no one here with the time to do it (those that REALLY know this stuff). So here I am.

    I have an HTA that displays buttons, that will eventually kick off a series of powershell scripts (using the vbs connection you’ve presented in this tutorial). However, what I would like to do is to run these scripts under a domain admin security context, which would be domain\sysadminuser. Manually, the sysadmins just run ps using the “run as a different user”, that opens the ps shell and they execute the scripts.

    I would like to have the HTA prompt for user credentials, pass those credentials onto the PS script so that it opens under that admin’s credentials and execute the script. I’d like to have the output that is normally directed in the active ps shell, redirected to the HTA.

    Would you be so kind as to show me how I’d do that?

    1. Hi Monterio,

      Thanks for the kind words, happy that this has helped you in some way.

      Ok well first off you need to decide if you want to pass the collected credentials (especially password) to the PowerShell script as clear text or encrypted. It is possible to encrypt and decrypt the password however this is pretty complex so I won’t go into that for now.

      If you are happy to pass the credentials as clear text then just follow steps 1, 2 and 3 in the article as this shows you how to collect a input from the user on the HTA side, process it and then pass it to PowerShell. Simply repeat these steps for each input you want to pass (e.g. username, password, etc).

      If you are going to collect a password then I would recommend you use the following HTML form element < input type="password" name="pwd" > (without the spaces between < and >) as this will make the password show as stars or dots in the input box as oppposed to plain text.

      Now that you have passed the credentials as clear text to PowerShell you need to use them. Most PowerShell cmdlets allow you to use the standard -Credential parameter which essentially allows you to run the cmdlet using the specified credentials as opposed to in the context of the user executing the script.

      To use the -Credential parameter, the credentials need to be stored in a System.Management.Automation.PSCredential object and to do this the password must be stored as a SecureString.

      To convert the password to a SecureString you can use the ConvertTo-SecureString PowerShell cmdlet however from past experience it might take you a while to get your head around using the cmdlet correctly. For more information about the ConvertTo-SecureString cmdlet have a look here.

      Alternatively, a much easier and quicker solution would be to not collect the credentials in the HTA but rather using the Get-Credential cmdlet, prompt the user for credentials when the PowerShell script executes. Doing this will automatically set the password as a SecureString (i.e. encrypted) as well as store the collected credentials in a PSCredential variable.

      All you then need to do is pass these credentials to the -Credential parameter on the cmdlet(s) you want to execute in this security context. For more information on the Get-Credential cmdlet, have a look here.

      Now to return the results and display them in the HTA, simply follow steps 5 and 6 in the article and this should help you get that set-up and running.

      I hope all this info helps you and you can get your script up and running.

      Regards,
      Luca

  2. No matter what, if I just use your base code, and copy both files to c:\temp. All I ever get is ‘ Warning Result: ‘ in yellow.
    nothing else, I cant make your base script work, is there something else I need or do before trying to run your script?

    1. Hi Bill, can you please post the code you are using so I can take a look at what is happening? You will need to change line 25 in the HTA file to point to the correct location and file name of the PowerShell (.ps1) file. Do you know if you have done this?

      Thanks
      Luca

  3. I want to start by saying this is a great post! Since you mention using a text file to pass data, you may want to include a couple of things in your example just to avoid confusion.
    The sResultsTxtFile variable needs to be declared.
    The sResultsTxtFile needs to point to the text file you want to read.
    A FileSystemObject needs to be created.
    I added the sResultsTxtFile variable to the existing DIM statement in Sub ExecutePowerShell, but for the sake of an example here’s some working code.

    ‘Collect result from PowerShell (via text file)
    Dim sResultsTxtFile
    sResultsTxtFile = “C:\PowerShellOutput.txt”
    Set oFSO = CreateObject(“Scripting.FileSystemObject”)
    Set oFile = oFSO.OpenTextFile(sResultsTxtFile, 1)
    Do Until oFile.AtEndOfStream
    sResultData = sResultData & oFile.ReadLine & “”
    Loop
    oFile.Close

    Just a thought to help anyone that may be a vbscript greenhorn like myself

  4. When I run this, I get an error saying “the system cannot find the file specified” pointing back to the location of my file on line 25. The file is obviously there, so I’m wondering if there maybe an issue with the quotation of the statement? I’ve ruled out folder permissions. Thanks

  5. How would you handle passing multiple parameters to PowerShell?
    I tried this and it Kind of works but only the first part of the data is getting to PowerShell.
    I changed this
    sCmd = “powershell.exe C:\Temp\PowerShell_HTA_Example.ps1 -Message ” & Chr(39) & sMessage & Chr(39)
    To
    sCmd = “powershell.exe C:\Temp\PowerShell_HTA_Example.ps1 -User “”” & sUser & “”” -Addl4 “”” & sAddl4 & “”” -Addl3 “”” & sAddl3 & “””
    For example if the user input text box is “John Doe” All I get in PowerShell is “John”
    Any help would be greatly appreciated.

    1. Hi Rudy,

      You will need to specify each parameter on line one in the PowerShell script, similar to the example below:

      Param([Parameter(Mandatory=$true)][string]$User, [Parameter(Mandatory=$true)][string]$sUser, [Parameter(Mandatory=$true)][string]$AddI4, [Parameter(Mandatory=$true)][string]$sAddI4, [Parameter(Mandatory=$true)][string]$AddI3, [Parameter(Mandatory=$true)][string]$sAddI3)

      You should then be able to pass the data and store them in the appropriate variables within the PowerShell script.

      Thanks
      Luca

  6. Hi Luca, Thank you for posting this tutorial, very informative and easy ish to follow even for a beginning. I have put both the .hta file .ps1 in c:\temp and kept the same file names. When i run the .hta and type in eeeeee and execute, the results just says Warning Results in orange. If a I enter Error again the results is Warning Results in orange.

    Any help would be greatly appreciated.

    1. Hi SiBox,

      What is your PS script doing with the input? If you are getting Warning results all the time, then maybe have a look at $ExitCode variable in PS, because the vbScript will see an exit code of 1 as a warning. That might be the issue.

      Hope that helps
      Luca

      1. Hi Luca,

        Thanks for your reply. I don't think the PS script is doing anything. I'm getting the impression that the HTA script isn't calling the PS script properly or at all. I have copied and pasted the path from the HTA script into run and it opens OK. When I run the PS script inside powershell ISE a window opens and what ever I type goes to the clipboard with '- This has been PowerShell processed'. I have looked at $ExitCode in the PS script and it all looks OK, but I'm a PS noob.

        Thanks

  7. Very use full and informative session….Any idea how to get multiple values back from powershell.

    Thanks

    1. Hi Sourav,

      “Step 4: Return output to HTA” in the article will show you how to do this. If you have a lot of data or values that you need to get back from PowerShell into your vbScript then the best way is to just save it in a text file in PowerShell and then read that text file into vbScript.

      Hope this helps
      Luca

  8. For later versions of powershell you’ll need to update your call to powershell.exe to include the -File parameter:

    powershell.exe -File C:\Temp\PowerShell_HTA_Example.ps1

    Also, you could avoid hard-coding your path (and issues with spaces) by using:

    Set objShell = CreateObject("WScript.Shell")
    sCmd = "powershell.exe -File """ & objShell.CurrentDirectory & "\PowerShellGUI.ps1""

    I’d also encourage folks to check out powershell.exe -encodedCommand where you can pass in the entire script encoded in base64 and eliminate the need for a proxy .ps1 script at all.

    1. Thanks Marc, yeah I wrote this during the PowerShell 2.0 days. Interesting about the -encodedCommand, I didn’t know about that – I will check it out! Thanks.

Leave a Comment