Archive

Archive for February, 2009

Packaging a .NET Deployment Project into a Single EXE

February 13th, 2009 Matt J. Wilson 1 comment

So you want to package your MSI and EXE into a single EXE for distribution?  There’s no way to do it using .NET, but Inno Setup is an excellent, free utility that will allow us to create a bootstrap utility.  You will also need to download the ISTool pack to utilize the isxdl.dll.

This tool will ensure the user has the proper .NET framework installed; if not, it will retrieve it from the Microsoft site and begin installing it for them.   It will also retrieve your EXE and MSI files from any web location you specify, allowing them to automatically download and install (or uninstall later) for the user.

Enough rambling, here’s the code to use and enjoy (please note my link to the .NET Framework and registry checking are for 2.0):

[_ISTool]
EnableISX=true

[Setup]
AppName=YourAppName
AppVerName=Your App Version 1.0.0
MinVersion=4.1,4.0
DefaultDirName={pf}\Your Default Directory
DefaultGroupName=Your Default Group Name
Compression=lzma
SolidCompression=true
OutputBaseFilename=Your Output File Name
DisableDirPage=true
DisableProgramGroupPage=true
DisableReadyPage=false
DisableReadyMemo=true

[Files]
Source: C:\Program Files\ISTool\isxdl.dll; Flags: dontcopy

[Messages]
WinVersionTooLowError=YourAppName requires Windows 2000, Windows XP or later.

[Icons]
Name: {group}\Uninstall YourAppName; Filename: {uninstallexe}

[Code]
var
dotnetRedistPath: string;
downloadNeeded: boolean;
dotNetNeeded: boolean;
memoDependenciesNeeded: string;
uninstaller: String;
ErrorCode: Integer;

procedure isxdl_AddFile(URL, Filename: PChar);
external 'isxdl_AddFile@files:isxdl.dll stdcall';
function isxdl_DownloadFiles(hWnd: Integer): Integer;
external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
function isxdl_SetOption(Option, Value: PChar): Integer;
external 'isxdl_SetOption@files:isxdl.dll stdcall';

const
dotnetRedistURL = 'http://download.microsoft.com/download/5/6/7/567758a3-759e-473e-bf8f-52154438565a/dotnetfx.exe';
const
exeURL = 'http://www.pathtoyoursite.com/setup.exe';
const
msiURL = 'http://www.pathtoyoursite.com/Installation.msi';

function InitializeSetup(): Boolean;

begin
Result := true;
dotNetNeeded := false;

if not FileExists(ExpandConstant('{tmp}\setup.exe')) or not FileExists(ExpandConstant('{tmp}\Installation.msi'))  then
begin
isxdl_AddFile(exeURL, ExpandConstant('{tmp}\setup.exe'));
isxdl_AddFile(msiURL, ExpandConstant('{tmp}\Installation.msi'));
downloadNeeded := true;
end

// See if the .NET Framework is already installed
if (not RegKeyExists(HKLM, 'Software\Microsoft\.NETFramework\policy\v2.0')) then begin
dotNetNeeded := true;
if (not IsAdminLoggedOn()) then begin
MsgBox('YourAppName needs the Microsoft .NET Framework to be installed by an Administrator', mbInformation, MB_OK);
Result := false;
end else begin
memoDependenciesNeeded := memoDependenciesNeeded + '      .NET Framework' ;
dotnetRedistPath := ExpandConstant('{src}\dotnetfx.exe');
if not FileExists(dotnetRedistPath) then begin
dotnetRedistPath := ExpandConstant('{tmp}\dotnetfx.exe');
if not FileExists(dotnetRedistPath) then begin
isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
downloadNeeded := true;
end;
end;
SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));
end;
end;

end;

function NextButtonClick(CurPage: Integer): Boolean;
var
hWnd: Integer;
ResultCode: Integer;

begin
Result := true;

if CurPage = wpReady then begin

hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));

// don't try to init isxdl if it's not needed because it will error on < ie 3
if downloadNeeded then begin

isxdl_SetOption('label', 'Downloading YourAppName Components');
isxdl_SetOption('description', 'YourAppName needs to install some components. Please wait while Setup is downloading extra files to your computer.');
if isxdl_DownloadFiles(hWnd) = 0 then Result := false;
end;
if (Result = true) and (dotNetNeeded = true) then begin
if Exec(ExpandConstant(dotnetRedistPath), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
// handle success if necessary; ResultCode contains the exit code
if not (ResultCode = 0) then begin
Result := false;
end;
end else begin
// handle failure if necessary; ResultCode contains the error code
Result := false;
end;
end;
FileCopy(ExpandConstant('{tmp}\Installation.msi'),  ExpandConstant('{app}\Installation.msi'), false);
FileCopy(ExpandConstant('{tmp}\setup.exe'),  ExpandConstant('{app}\setup.exe'), false);
end;
end;

function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
var
s: string;

begin
if memoDependenciesNeeded <> '' then s := s + 'Dependencies to install:' + NewLine + memoDependenciesNeeded + NewLine;
s := s + MemoDirInfo + NewLine + NewLine;

Result := s
end;

[Run]
Filename: msiexec.exe; Parameters: "/i ""{tmp}\Installation.msi "" /qb"

[UninstallRun]
Filename: msiexec.exe; Parameters: "/x ""{app}\Installation.msi "" /quiet"

Thanks to the various posts on the web that gave me the basic understanding of Inno Setup and allowed me to create this script.

Categories: Programming Tags: ,

Microsoft Excel Drivers and IMEX

February 13th, 2009 Matt J. Wilson 2 comments

For any developers that are ever forced to develop in VBA, such as myself, I take pity on you for having to deal with some of Microsoft’s idiotic software kinks.  My latest gripe?  The use of any built-in driver to query an Excel sheet.

Here’s an example of my sheet:

   NumberOne
1  123456
2  987654
3  135791
4  246802
5  503513
6  546516
7  889846
8  984658
9  Some Text Here

I tried multiple data source connections, such as these two:

dbConnectionString = "DRIVER={Microsoft Excel Driver (*.xls)};Readonly=1;DBQ=" & SourceFile
dbConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & SourceFile & ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'"

So the data I read in for my NumberOne column should be the following values: 123456, 987654, 135791, 246802, 503513, 546516, 889846, 984658, Some Text Here.

What do these actually produce: 123456, 987654, 135791, 246802, 503513, 546516, 889846, 984658, null.

Now why would a null result be produced? I specifically stated to use the IMEX parameter, but it is simply not enough to overcome the stupidity of Microsoft.  Any built-in Excel driver will query the first 8 rows of a sheet and then make a determination (without your permission or knowledge) as to what type of column it is, thereby ignoring anything that doesn’t meet this data type later in the sheet.  There are no exceptions, no warnings, and no way around it but to insert a “MICROSOFT_IS_STUPID” cell in one of these rows, that you then must explicitly ignore, to force the driver to stop it’s ridiculous assumptions and read all of your data.  Also, please note that having a header row won’t rectify the problem, you must have some intermixed data types in the first 8 rows of data.

Categories: Programming Tags: , ,

Posting a File using the WebBrowser Class

February 12th, 2009 Matt J. Wilson 2 comments

So you have a WebBrowser object in .NET that you would like to use to automatically post a file to a given location?  You can probably figure out how to set values on this page:

<input type="text" name="myTextField">
// Update myTextField with a value
webBrowser.Document.All["myTextField"].SetAttribute("value", "I set your value");

But how do you update this?

<input type="file" name="myFileField">

You can’t simply use the SetAttribute function, as there is no attribute to correctly set; this was done intentionally to prevent users from using shady Javascript to automatically capture and send files from a page.  So how do you do it?  Something even more shady:

HtmlElement element = webBrowser.Document.GetElementById("myFileField");
element.Focus();
System.Threading.Thread.Sleep(1000);

// Perform SendKeys to fake the browser into setting the file name
SendKeys.SendWait(@"C:\Path To My File\test.txt");
SendKeys.Flush();

I hope that Google picks this up and saves someone else the thirty minutes worth of their life that I wasted attempting to figure out a solution.

Categories: Programming Tags: , ,

Internet Explorer Toolbar Frustrations

February 10th, 2009 Matt J. Wilson No comments

I recently developed a framework for Internet Explorer toolbars and am currently attempting to do the same based on some FireFox toolbars I have written.  During this development, I used several tutorials available on the web and found myself with a repeating error on any non-development machines:

Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.

It turns out that a reference I was adding to the SHDocVW assembly was done incorrectly.  I simply looked at some sample projects and saw the reference pointing to the project’s “obj” directory, but this was incorrect!  The original reference should be added from your system32 directory.

An easy to way to diagnose if this is what’s happening to you:

  • Expand the “References” folder of your project
  • Look for a reference to SHDocVw
    • If this reference is exactly “SHDocVw”, this solution won’t fix your problem
    • If your reference is exactly “Interop.SHDocVw”, then remove the reference and add the one from your system32 directory

Inno Setup – The setup files are corrupted. Please obtain a new copy of the program.

February 10th, 2009 Matt J. Wilson No comments

During some recent work using Inno Setup to create a bootstrap application, several workstations were presented with the following error:

error 

 After hours upon hours of Googling, I still couldn’t find a fix that worked for me.  Upon more carefully examining my script, I noticed I had left the following line in my ISS file:

UninstallDisplayIcon={app}\icon25x25.ico

The problem was due to the fact that I never included this file under the [Files] area as I simply had no intention of ever using the icon.

So for future reference (and in case someone stumbles upon this through Google), always ensure any file reference is valid.  Your compiler and development machines may not throw an error, but if you plan to distribute this to several workstations this error may rear its ugly head.

UPDATE:

After fixing this file, I was still noticing an error on the hosting site (although any EXE file downloaded from my website worked flawlessly).  It turns out Redhat and PHP often don’t play too well together in this regard and need a few settings tweaked in httpd.conf, you may need to disable the EnableMMAP and/or EnableSendfile options.

Categories: Programming Tags: