Installshield: Prompt user for a license file, then copy it


I need to add a prompt to an installshield project that asks the user where the license key file is, then copy the file to the INSTALLDIR location.

This is going to be a fairly complex thing, First I am going to create an install script function that copies the file, then I am going to create two custom actions, one that executes the CopyLicenseFile installscript file, the other prompts for the File location using  a file browser. Then I am going to create a new dialog that will call the FileBrowse custom action.

I am assuming you have already created a basic MSI project.


I used the following references


Expand out Behavior and Logic, and click on InstallScript

Click on the Installscript, then right click on Files and choose New Script File

Comment out the same code

//export prototype MyFunction(HWND);


// Function: MyFunction
// Purpose: This function will be called by the script engine when
// Windows(TM) Installer executes your custom action (see the "To
// Do," above).
//function MyFunction(hMSI)
 // To Do: Declare local variables.

 // To Do: Write script that will be executed when MyFunction is called.


We are going to create an extry point function called ExFn_CopyLicenseFile Copy and Paste into the Setup.rul,

#include "ifx.h"
// Function: ExFn_CopyLicenseFile
// Purpose: This function will be called by the script engine when
// Windows(TM) Installer executes your custom action (see the "To
// Do," above).
// The Function reads from the Property LICENSEFILE and INSTALLDIR, then copies the
// Include Ifx.h for built-in InstallScript function prototypes.
#include "Ifx.h"
export prototype ExFn_CopyLicenceFile(HWND);
function ExFn_CopyLicenceFile(hMSI)
 NUMBER nResult;
 STRING licenseFilePath;
 NUMBER licenseFilePathBuffer;
 licenseFilePathBuffer = MAX_PATH;
 if(MsiGetProperty(hMSI, "LICENSEFILE", licenseFilePath, licenseFilePathBuffer) == ERROR_SUCCESS) then
 SprintfBox(INFORMATION,"Deferred Execution","The value of LICENSEFILE is %s",licenseFilePath);
 SprintfBox(INFORMATION,"Deferred Execution","The value of INSTALLDIR is %s",INSTALLDIR);

 // Copy all files in the source directory, including files
 // in subdirectories, to the target directory.
 nResult = CopyFile(licenseFilePath, INSTALLDIR + "license.lic"); 
 // Report the results of the copy operation.
 switch (nResult)
 case 0:
 MessageBox ("File successfully copied.", INFORMATION);
 MessageBox ("A target directory could not be created.", SEVERE);
 MessageBox ("Insufficient memory.", SEVERE);
 MessageBox ("Insufficint disk space.", SEVERE);
 MessageBox ("Unable to open the input files in "+ INSTALLDIR +".",
 MessageBox ("Unable to copy the source files.", SEVERE);
 MessageBox ("A target file already exists and cannot be overwritten.",
 MessageBox ("An unspecified error occurred.", SEVERE);

I have left in the MessageBox and the SprintfBox lines to aid in debugged, and would be removed for your production version.

You should now have file -> Setup.rul and Functions -> ExFn_CopyLicenseFile

Custom Actions

Expand out Behavior and Logic, then click Custom Actions and Sequences

Right click on Custom Actions and choose New InstallScript

Change name to “CopyLicenseFile”

Set ‘Function Name’ to ExFn_CopyLicenseFile

Set install Exec Sequence to After MsiConfigureService

Set the ‘Install Exec Condition’ to ‘NOT Installed AND NOT PATCH’ this will mean it only runs if the Product is not installed and this isn’t a Patch

Create another Custom action, New MSI DLL, Stored in Binary Table

Rename to FileBrowse


In the right hand pane

DLL Filename: <ISProductFolder>\redist\language independent\i386\FileBrowse.dll
Function Name: FileBrowse
Return Processing: Synchronous (Ignores exit code)
In-Script Execution: Immediate Execution
Execution Scheduling: Always execute
For all other settings, leave the default values. The value of the MSI Type Number setting should be 65.

Create Properties

Expand out Behavior and Logic, and click on Property Manager

Click on New

Rename to LICENSEFILE, since this starts with a CAPITAL it creates a global property that can be referenced later.


Create Custom Dialog

Expand out User Interface and click on Dialogs

Find the DestinationFolder Dialog,

Right Click and clone

Rename to LicenseFileLocation

We are doing to repeat the following steps for a number of resources, the goal is to create a new STRING property for each string we are changing, so we don’t accidentally change the strings in other dialogs.

Click on LicenFileLocation, English (United States)

Click on the string Destination Folder so it is highlighted

Click in the Text field where is says Destination Folder

Click on the …

Then Cick on New

Change Value to License File

Repeat for “Click next to install


Change Text to “Click Next to install, or click Change to change the source of the License File”, remember to do the three dot and then new to create a new string, or you will also change the value in the dialog you cloned.

Change “Install [ProductName] to:” to “Install [ProductName] License File:”

Click on  [INSTALLDIR]

Change Property to LICENSEFILE

Change Text: to [LICENSEFILE], remeber the three dots and create new string

Click on LicenseFileLocation -> Behavior

Click on ChangeFolder

Change SpawnDialog to DoAction, and Argument to FileBrowse

Change [_BrowseProperty] to [LICENSEFILE] and Argument to [IS_BROWSE_FILEBROWSED]

Click on Back

Change NewDialog to “Destination Folder”

Click on DestinationFolder -> Behavior

Click on Next

Change NewDIalog to LicenseFileLocation

Click on SetupType -> Behavior

Click on Back

Change NewDialog to LicenseFileLocation


Now the moment of truth, build and test, Why are not setting a default license file so it is pulling the default value from the property which is “0”

Click on Change…


Succcess 🙂

Installshield Register.Net DLLs

My task is I need to register  a .net dll as part of the install.

First thing I am going to do is Create a Basic MSI Project

Approach 1

Create a component with just the .net dll in it

Make sure you have clicked the Installation Designer Tab, then expand out ‘Organization’, then select ‘Setup Design’, then select TestDLLRegister_Files

Right Click, choose ‘New Component’

Now expand out TestComonent, and choose ‘Files’

right click and choose ‘Add’ and select the dll you want to register.

Now it should show up in the files listing.

To set the .dll to be the keyfile, select the dll, then choose ‘Set Key File’

You will now see the key icon next to the DLL

Click on TestComponent

You should see

Change the .net Interop to yes

Installshield in the background is using regasm /regfile to create a file that contains the registry changes needed, then includes those changes as part of the install.

For some reason, perhaps that I am installing a 32bit component on a 64bit machine, this didn’t work as expected. After install the target machine contained all the registry enteries that the regasm /regfile creates but it didn’t work, I have read that some DLL’s perform post registration actions the run when you run RegAsm.exe VirtualCam.dll /nologo /codebase but don’t get captured when you run RegAsm.exe VirtualCam.dll /nologo /codebase /regfile

Approach 2

Run a .bat file to register the files.

Create 2 batch files, one that registers the dll and the other that unregisters

C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe VirtualCam.dll /nologo /codebase


C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe /unregister /nologo VirtualCam.dll

Create a new Component

Expand out the new component you created Test2Comonent, and click on ‘Files’

Add files

Make sure it will install your files to [INSTALLDIR] by clicking on Test2Component and checking it says ‘Destination’ ‘[INSTALLDIR]’

Expand out ‘Behavior and Logic’ and choose ‘Custom Actions and Sequences’

Right click on custom actions

Choose new exe -> Path references a file

Set ‘Working Directory’ to ‘INSTALLDIR’

Set ‘File Name & Command LIne’ to ‘[SystemFolder]cmd.exe /c /install.bat’

Set ‘Return Processing’ to  ‘Syncronous (Check exit code)’

Set ‘In-Script Execution’ to ‘Immediate Execution’

Set ‘Execution Secheduling’ to ‘Always execute’

This should give you an MSI type of 34

Change the Install Exec Sequence to ‘After InstallFinalize’

Set the ‘Install Exec Condition’ to ‘NOT Installed AND NOT PATCH’ this will mean it only runs if the Product is not installed and this isn’t a Parch

Rename the Custom Action to ‘RegisterDLL’

Repeat for  UnRegisterDLL, except this time you are executing uninstall.bat

This time the ‘Install Exec Sequence’ is “After RemoveIniValues’ and ‘Install Exec Condition’ is “Remove ~=”ALL” so this runs only when all components have been removed and the app is being uninstalled.

Now Assuming everything worked you will see the black dos box during the install and your DLL will be registered.

Create a Basic MSI Project

Choose Basic MSI, enter a project Name, Enter  location

Click on Application Information

Enter your Company Name, and Company Web address

Click on Installation Interview

Do you want to display a License Agreement Dialog: No

Do you want to prompt users to enter their Company Name and User Name: No

Do you want your user to be abel to modify the installation location of your application: Yes

Do you want users to be able to selectively install only certain parts of your application: Yes

Do you want to give users the option to launch your application when the installation completes: No

Choose Build Installation

Choose Single Executable

Build Installations

Click on Installation Designer

Change the INSTALLDIR to remove the Inc. from the name

Change the Project File Format to XML, I always do this so I can track changes to the installer file in source control like git


Adding a custom dialog to and Installshield Basic MSI project

I needed to add a new custom dialogue to a MSI basic project that captures a new path for the data folder for a project. The dialog should take the INSTALLPATH add DATADIR, then allow the user to change the path. On install the DATADIR path will be inserted into an app.config xml file so the application knows where to look

Create a new Basic MSI Project


Application Information


Installation Interview


Installation Designer


Create new component




Click on the three dots Buttons next to Destination



Click on the three dots Buttons next to Destination




New Component Files



New Component Files


Configure XML Changes


import the XML file



Choose only the elements you need to change



Take out the xpath information that searches for specific elements in the config file, we just want it to match on runtime element, not the dataFolder orenviromentId


Change the dataFolder Parameter to [DATAFOLDER]


Delete the environmentID since we are not changing that


Navigate to Dialogs


right click Clone


Rename the dialog


Select the Location Control


Create new string for the control you are changing by clicking on the three dots



Click New


Enter Value: [DATAFOLDER]



Select DlgTitle Control


Select the three dots the text



Select New


Enter  DataFolder


Select LocLabel


Click three dots next to Text



Click New


Enter “Choose Data Directory for [ProductName]:”


Select Bahavior


Select ChangeFolder





Select Destination Folder


Select Next


Change NewDialog to DataFolder



Select DataFolder


Select Back


Change New Dialog to Destination Folder



Goto ReadyToInstall


Go to Back


Change DestinationFolder to DataFolder


To DataFolder


Now do a build, and you install, you should see

Screen Shot 2014-08-22 at 3.41.32 PM

And when you check your XML file you should see the changes reflected there.

Configuring a Aeon Labs Multi Sensor with openHAB

I got a new sensor today, an Aeon Labs Aeotec Z-Wave Multi-Sensor
. This is a motion sensor that can report Light, Humidity, Temperature, Motion.

The first thing is to add the sensor to your z-wave network. I read something interesting in searches for how to set this up. If you configure your device at one location say your desk then move the sensor to another location you need to update the neighbors using ozwcp (for a guide on how to compile it on centos 6 see my last blog post).

I am using an Aeon DSA02203-ZWUS Labs Z-Wave Z-Stick Series 2 USB Dongle
, so I unplug it from the USB port then push the button, then push the black button on the Multi Sensor and it is joined to the network.

Then start ozwcp

./ozwcp -d -p 1234

Now make sure the red light is on for the sensor so we know it is awake, if not push the black button. Enter the device name, I have a special configuration so it always maps to /dev/zwave ( will write up how to do that later ). Click on initialize

Screen Shot 2014-07-12 at 12.37.58 AM

Select the multinode sensor (note is says awake)
Screen Shot 2014-07-12 at 12.41.04 AM

select Request Node Neighbor Update and press Go

Screen Shot 2014-07-12 at 12.42.19 AM

wait for the log window to stop updating.

Select configuration for the Sensor

Screen Shot 2014-07-12 at 12.47.49 AM

Set Group 1 Reports: to 225, which is decimal for the binary map 11100001, the first bit reports the battery, the 5th bit Temperature, the 6th bit Humidity, the 7th bit Luminance, then click the submit button next to it. I also set the Group 1 Interval: to 240 (4 minutes) while testing, again click the submit button next to it. Make sure the log messages have finished updating then press refresh to make sure the setting have taken. Once finished click on the close button in the Controller Interface box.

In open hab configure the following items


Group Motion
Number sensor_1_temp "Temperature [%.1f °F]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=1"}
Number sensor_1_humidity "Humidity    [%.0f %%]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=5"}
Number sensor_1_luminance "Luminance    [%.0f Lux]" (Motion) {zwave="2:1:command=sensor_multilevel,sensor_type=3"}
Contact sensor_1_motion "sensor [MAP(]" (Motion) {zwave="2:1:command=sensor_binary,respond_to_basic=true"}
Number sensor_1_battery "Battery [%s %%]" (Motion) {zwave="2:command=battery"}


import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*

var Number counter = 0
var Number lastCheck = 0

rule "corLightOn"
        Item sensor_1_motion changed from CLOSED to OPEN
        sendCommand(light_living_room_switch, ON)

rule "corLightOff"
        Item sensor_1_motion changed from OPEN to CLOSED
        sendCommand(light_living_room_switch, OFF)


sitemap home label="Main Menu"
        Frame {
                Group item=Motion label="Motion" icon="firstfloor"



CLOSED=No Motion
-=No Motion

if everything goes to plan you should see the following when you visit http://hostname:8080/

Screen Shot 2014-07-12 at 1.08.12 AM

And when you move in front of the motion detector it should turn on the light_living_room_switch, and turn it off 4 minutes later if no motion detected.

Compiling owzcp on Centos 6

I using a I am using an Aeon DSA02203-ZWUS Labs Z-Wave Z-Stick Series 2 USB Dongle with openHAB, and I have found that OpenZWave Control Pannel is a very useful companion tool for configuring and testing Z-Wave devices before you add them to openHAB

mkdir svn
cd svn
svn checkout open-zwave
svn checkout openzwave-control-panel-read-only
sudo yum install libudev-devel
cd ..
tar zxvf libmicrohttpd-0.9.19.tar.gz
mv libmicrohttpd-0.9.19 libmicrohttpd
cd libmicrohttpd
cd ..
cd open-zwave

uncomment the following lines and change the LIBZWAVE to match

# for Linux uncomment out next three lines
LIBZWAVE := $(wildcard $(OPENZWAVE)/*.a)
LIBUSB := -ludev
ln -s ../open-zwave/config/ .
./ozwcp -d -p 1234

bring up a browers http://server:1234

I have my zwave configured for /dev/zwave (which i will post a blog in in the future)

Planning for future employment

I read an article today in the Washington Post. In IT I feel there is great value in moving from one company to another every 3-4 years, if for no other reason than it exposes you to new technologies and gives you a chance to jettison the technical debt you have become attached to over your tenure in your current position.

I have changed employers a number of times recently, one by choice, and one by proxy of the company I worked for being sold and purchased by another company. The biggest stress of changing jobs for me as been the upheaval in dealing with changing benefits.

If the tech industry is going to embrace the ‘tour of duty model’ described in the article then we also need to address being able to carry benefits over from one company to another.

For Medical make the companies contribution a deposit into a medical account like an HSA that you can use to pay for your own medical plan. If a company wants to negotiate a group rate then they still can, but I should have the choice to use it or not. When I leave a company I can continue to stay enrolled in my old companies plan. My old company doesn’t have to pay for me, but the company gets the benefit of theoretically larger enrollee pool if people like your plan. This idea probably falls apart when you remember that a lot of companies self insure, so they and you are not really paying for insurance, but you are contributing to the company pool that the company hopes will cover the expenses for the year, some years they loose money other years they make money.

The same thing goes for a 401K plan, lets get rid of vesting periods, then move the 401K from a company plan to a personal plan. If the company wants to negotiate a company 401k Plan that employees use great, if not then money is electronically deposited into my personal 401K plan. If the company wants to match on the 401k then that would get sent to my personal 401K plan as well. Before you go, oh no, companies will never get rid of the vesting period, remember we are talking about a 2 or 4 year tour of duty, so most employees would ever reach fully vested anyway, so lets make it an honest match rather than the bate and switch we currently have. If employers want employees to stay then increase the match based on anniversaries.

extending a Lennox Icomfort Thermostat: Part 1

See part 2 in my investigation

I recently had installed a new air conditioning system and it came with a new Lennox iComfort Thermostat that talks to the cloud over a wifi connection. After spending a little time looking at messages that get passed back and forward from chrome to the cloud service using the chrome developer tool, it looks like a fairly straight forward sort of REST api.

I managed to get some ruby code that log in, then queries the status of the the thermostat.

The authentication is a pain, since you log into the website using an ASP.NET web application, so it needs the __VIEWSTATE and the __EVENTVALIDATION form fields to be present. I have hardcoded them for the moment, but intended to pull them out of the of the response on the first page. I should set up a proxy and see if the android app uses a simpler API for authenticating.

Once authenticated you can send a JSON message like


and you get back

    "Friendly_Name"=>"Air Handler",
    "Control_Model_Nbr"=>"Equipment Interface Module ? AHC",
    "Friendly_Name"=>"Wi-Fi Controller",
    "Zone_Name"=>"Zone 1",
    "Schedule_Name"=>"0|summer^1|winter^2|spring fall^3|save energy^4|custom",
 "CurrentZoneName"=>"Zone 1",
 "CurrentDate"=>"Jul-09-2014 09:33:51 PM",
 "System_Name"=>"my icomfort system",
  "cool only,2&heat only,1&heat or cool,3&off,0&emergency heat,4&"}

As you can see lots of useful information. Short term I am planning on collecting stats about how long it takes the system to cool and how long it is running. The cloud site only updates when there is a change event, so the actual data that will be written is fairly small. Long term I want to be able to hook it into my Home Automation system, which is currently OpenHab. Depending on how far I get I will post my ruby code online, but nothing I am doing is hard to replicate.


A request came in for the sample ruby code i wrote, I originally didn’t want to post this, since this was just something I hacked together. You will need to need to log into the icomfort website first, then use a tool like the gooogle chrome developer tools to grab the __VIEWSTATE, __EVENTVALIDATION cookie, and update ctl00$RightContent$txtUserName, ctl00$RightContent$txtPwd it, hidden_gateway_SN, userid. Again apologies in advance for my hacked together example.

require 'rest_client'
require 'json'
require 'uri'
require 'pp'

#RestClient.proxy = "http://localhost:8008"

response3 = RestClient.get ''
# => {"_applicatioN_session_id" => "1234"}
p response3.cookies
#p response3
@session_cookies = response3.cookies

response2 =
    "__LASTFOCUS" => "",
    "__EVENTTARGET=" => "",
    "__EVENTARGUMENT=" => "",
#    "__VIEWSTATE" => "",
#    "__EVENTVALIDATION" => '',
    'ctl00$RightContent$hdnPwd' => "",
    'ctl00$RightContent$txtUserName' => "<REPLACE WITH YOUR USERNAME>",
    'ctl00$RightContent$txtPwd' => "<REPLACE WITH YOUR PASSWORD>",
    'ctl00$RightContent$chkRemember' => "on",
    'ctl00$RightContent$btnLogin' => "Log in"},
  {:cookies => @session_cookies}
) { |response, request, result, &block|
  if [301, 302, 307].include? response.code
    p response.cookies
    @session_cookies = response.cookies
    #response.follow_redirection(request, result, &block)
    p response
    response.return!(request, result, &block)
#p response2.cookies
#p response2.code
p @session_cookies

my_hash = { 
:hidden_gateway_SN =>"<REPLACE WITH YOUR thermostat serial number>",
:pref_temp_units => "0",
:Central_Zoned_Away => "2",
:Cancel_Away => "-1",
:current_prg => "2",
:current_mode => "1",
:CurrentBrowser =>"chrome",
:zoneNumber => "0",
:alertTypes => "1",
:reminderTypes => "0"}

myjson = JSON.generate(my_hash)

p "CurrentDate,Cool_Set_Point,Indoor_Temp,Fan_Mode,Indoor_Humidity,System_Status"

while 1 do
  response4 =
    {:content_type => :json, :accept => :json, :cookies => @session_cookies} )  
  #{ |response, request, result, &block|
  #  if [301, 302, 307].include? response.code
  #    p response.cookies
  #    @session_cookies = response.cookies
  #    #response.follow_redirection(request, result, &block)
  #    response
  #  else
  #    p response
  #    response.return!(request, result, &block)
  #  end

  json_object = JSON.parse(response4)
  d = JSON.parse(json_object['d'])
#  pp d
#  if #{lastupdate} != #{d['CurrentDate']}
   if lastupdate != d['CurrentDate']
    lastupdate = d['CurrentDate']
    p "#{d['CurrentDate']},#{d['Cool_Set_Point']},#{d['Indoor_Temp']},#{d['Fan_Mode']},#{d['Indoor_Humidity']},#{d['System_Status']}"
  sleep 1

Install HyperV or another feature using installshield

I am using installshield 2013, but this may apply to other versions.

My goal was to be able install the hyperV feature on a windows 2012 server using installshield.

First you follow the directions at

Add the property IS_PS_EXECUTIONPOLICY and set it to Unrestricted


Add a Predefined System Search to check for powershell

InstallShield Add System Search

Installshield system search powershell

Create a new custom action. I am using a powershell from a binary table

InstallShield Add custom action

I am being lazy so the script I am running is on my desktop
Add Install Exec Sequence: After InstallFinalize
Add Install Exec Condition: POWERSHELLVERSION

InstallShield PowerShell custom Action

Now comes the magic,
Installshield runs using 32 bit mode by default, which means it also runs the 32bit powershell verison, From what I can tell the 32bit version of PowerShell doesn’t implement a bunch of stuff, so you either have to convert your installshield project to strict 64bit or put some magic at the top of you Powershell script to switch it to 64bit

To set your installshield project to strict 64bit (see

set the template x64; 1033
Installshield 64bit template

then you must tell the release to be strict about using 64bit components only

InstallShield Release Strict

The other option is to use the magic from I choose to use the magic at the top of my power shell since I may have to install 32 bit components as part of my installer, so I didn’t want to be restricted to only installing 64bit components.

# am I running in 32 bit shell?
if ($pshome -like "*syswow64*") {
    write-warning "Restarting script under 64 bit powershell"

    # relaunch this script under 64 bit shell
    # if you want powershell 2.0, add -version 2 *before* -file parameter
    & (join-path ($pshome -replace "syswow64", "sysnative") powershell.exe) -file `
        (join-path $psscriptroot $myinvocation.mycommand) @args

    # exit 32 bit script

# start of script for 64 bit powershell

 whoami | Add-Content -Path c:\file.txt
 echo "check admin" | Add-Content -Path c:\file.txt
 $64bit = [Environment]::Is64BitProcess

 ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
        [Security.Principal.WindowsBuiltInRole] "Administrator") | Add-Content -Path c:\file.txt

Add-Content -Path c:\file.txt -Value (’64-bit process = ‘ + $64bit);
echo "check feature" | Add-Content -Path c:\file.txt
Get-WindowsFeature | Where-Object {$_.Name -eq "Hyper-V"}  | Add-Content -Path c:\file.txt
$check = Get-WindowsFeature | Where-Object {$_.Name -eq "Hyper-V"}
If ($check.Installed -ne "True") {
        echo "notInstalled" | Add-Content -Path c:\file.txt
        #Install/Enable SNMP Services
        #Add-WindowsFeature SNMP-Services | Out-Null
        echo $check.Installed | Add-Content -Path c:\file.txt
        echo "Do Install" | Add-Content -Path c:\file.txt
        Install-WindowsFeature –Name Hyper-V -IncludeManagementTools -whatif | Out-File -Append c:\file.txt
else {
  echo "Installed" | Add-Content -Path c:\file.txt

Please excuse the c:\file.txt, those were my way of debugging to see what was going on.

I still need to work on handling the required reboot, but that is tomorrows problem

Start Microsoft Remote Desktop Session from command line on mac

You need to have at least 8.0.6 installed.

Start a command prompt

open "rdp://full address=s: mode id=i:1&Desktopheight=i:1024&Desktopwidth=i:1280"

I am still trying to workout how to pass a username with a domain, when I put in &username=s:localhost\administrator it appears in the login box as


For a full list of commands that should work see