Inno Setup IIS Installation and Configuration - asp.net

I have been trying to figure out how to install and register IIS using an Inno Setup Script, but I have so far been unsuccessful.
I need to create an application, application pool with .Net version 4 and a virtual directory, get the machine's IP and proceed to edit the bindings of the website with this IP.
So far all that works in my install is checking to see if IIS is installed.
If anyone has ever done anything like this before, I would really appreciate if you could share your script.
Thank you.

Here is my complete code to handle IIS (Internet Information Services) with Inno Setup.
It contains:
Installation of .NET Framework 4
Install/Uninstall of IIS+ASP.NET 4
Registration and removal of Web-Sites/Apps
Helper to get user-account an app pool is running on
Helper to get physical path to a Web-Site
It has been tested with the following Windows editions:
XP Pro SP3 x86, 2003 R2 x86, 2003 R2 x64, Vista Ultimate x64, 7 Home Premium x64, 2008 R2 x64, 2011 x64, 8.1 Pro x86, 10 Pro x64
Everything is done as passive as possible to not break any existing Web-Sites.
Important
This might need the Unicode-Version of Inno-Setup (I'm using 5.5.6(u)).
It is also required to force 64-bit mode on x64 machines (otherwise registry and file access will redirect to 32-bit stuff):
[Setup]
ArchitecturesInstallIn64BitMode=x64
Required Helpers
Some constants
ExpandEnvironmentStrings => https://msdn.microsoft.com/en-us/library/windows/desktop/ms724265%28v=vs.85%29.aspx
ExecWithResult => Reads what is written to STDOUT
IIs7ExecAppCmd => Required for IIS installation since Vista/2008
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
ERROR_SUCCESS = 0;
ERROR_INVALID_FUNCTION = 1;
ERROR_NOT_SUPPORTED = 50;
ERROR_NOT_FOUND = 1168;
ERROR_SUCCESS_REBOOT_REQUIRED = 3010;
function ExpandEnvironmentStrings(lpSrc: String; lpDst: String; nSize: DWORD): DWORD;
external 'ExpandEnvironmentStrings{#AW}#kernel32.dll stdcall';
function ExpandEnvVars(const Input: String): String;
var
Buf: String;
BufSize: DWORD;
begin
BufSize := ExpandEnvironmentStrings(Input, #0, 0);
if BufSize > 0 then
begin
SetLength(Buf, BufSize);
if ExpandEnvironmentStrings(Input, Buf, BufSize) = 0 then
RaiseException(Format('Expanding env. strings failed. %s', [SysErrorMessage(DLLGetLastError)]));
#if AW == "A"
Result := Copy(Buf, 1, BufSize - 2);
#else
Result := Copy(Buf, 1, BufSize - 1);
#endif
end
else
RaiseException(Format('Expanding env. strings failed. %s', [SysErrorMessage(DLLGetLastError)]));
end;
// Exec with output stored in result. ResultString will only be altered if True is returned.
function ExecWithResult(const Filename, Params, WorkingDir: String; const ShowCmd: Integer; const Wait: TExecWait; var ResultCode: Integer; var ResultString: String): Boolean;
var
TempFilename: String;
TempAnsiStr: AnsiString;
Command: String;
begin
TempFilename := ExpandConstant('{tmp}\~execwithresult.txt');
// Exec via cmd and redirect output to file. Must use special string-behavior to work.
Command := Format('"%s" /S /C ""%s" %s > "%s""', [ExpandConstant('{cmd}'), Filename, Params, TempFilename]);
Result := Exec(ExpandConstant('{cmd}'), Command, WorkingDir, ShowCmd, Wait, ResultCode);
if not Result then
Exit;
LoadStringFromFile(TempFilename, TempAnsiStr); // Cannot fail
ResultString := String(TempAnsiStr);
DeleteFile(TempFilename);
// Remove new-line at the end
if (Length(ResultString) >= 2) and (ResultString[Length(ResultString) - 1] = #13) and (ResultString[Length(ResultString)] = #10) then
Delete(ResultString, Length(ResultString) - 1, 2);
end;
function IIs7ExecAppCmd(Params: String; var ResultString: String; var ResultCode: Integer): Boolean;
var
AppCmdFilePath: String;
Command: String;
begin
AppCmdFilePath := ExpandConstant('{sys}\inetsrv\appcmd.exe');
Result := ExecWithResult(AppCmdFilePath, Params, '', SW_HIDE, ewWaitUntilTerminated, ResultCode, ResultString);
end;
.NET 4 Installation
Will install .NET 4.0 for Win XP/2003. .NET 4.6 for Vista/2008 and later.
Will do nothing if .NET 4 is already included with the system (Win 8/2012/10).
The following files are required and must be included in the setup:
.NET 4.0 Web-Installer "dotNetFx40_Full_setup.exe" => https://www.microsoft.com/en-US/download/details.aspx?id=17851
.NET 4.6 Web-Installer "NDP46-KB3045560-Web.exe" => https://www.microsoft.com/de-de/download/details.aspx?id=48130
WIC x64 "wic_x64_enu.exe" => https://www.microsoft.com/de-de/download/details.aspx?id=1385
WIC x86 "wic_x86_enu.exe" => https://www.microsoft.com/de-de/download/details.aspx?id=32
-
[Files]
Source: "dotNetFx40_Full_setup.exe"; DestDir: "{tmp}"; Flags: ignoreversion dontcopy
Source: "NDP46-KB3045560-Web.exe"; DestDir: "{tmp}"; Flags: ignoreversion dontcopy
Source: "wic_x64_enu.exe"; DestDir: "{tmp}"; Flags: ignoreversion dontcopy
Source: "wic_x86_enu.exe"; DestDir: "{tmp}"; Flags: ignoreversion dontcopy
Since I'm using the Web-Installers, an internet connection might be required during installation.
// Checks if .NET 4 "Full" is installed
function IsDotNet4FullInstalled(): Boolean;
var
Success: Boolean;
Install: Cardinal;
begin
try
ExpandConstant('{dotnet40}'); // This will throw an exception if .NET 4 is not installed at all
Success := RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Install', Install); // Check for "Full" version
Result := Success and (Install = 1);
except
Result := False;
end;
end;
// Returns True if a restart is required.
function InstallDotNet4(): Boolean;
var
Version: TWindowsVersion;
ResultCode: Integer;
Success: Boolean;
begin
GetWindowsVersionEx(Version);
if (Version.Major <= 5) then // 4.0 for XP, 2003
ExtractTemporaryFile('dotNetFx40_Full_setup.exe')
else // 4.6
ExtractTemporaryFile('NDP46-KB3045560-Web.exe');
if (Version.Major <= 5) then // XP, 2003: Install .NET 4.0
begin
Success := Exec(ExpandConstant('{tmp}\dotNetFx40_Full_setup.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if Success and (ResultCode = 5100) then // Indicates that "Windows Imaging Component" is missing (probably 2003)
begin
if IsWin64 then
begin
ExtractTemporaryFile('wic_x64_enu.exe');
if not Exec(ExpandConstant('{tmp}\wic_x64_enu.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> ERROR_SUCCESS) then
RaiseException('Failed to install "Windows Imaging Component": ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
// Retry .NET
Success := Exec(ExpandConstant('{tmp}\dotNetFx40_Full_setup.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end
else
begin
ExtractTemporaryFile('wic_x86_enu.exe');
if not Exec(ExpandConstant('{tmp}\wic_x86_enu.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> ERROR_SUCCESS) then
RaiseException('Failed to install "Windows Imaging Component": ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
// Retry .NET
Success := Exec(ExpandConstant('{tmp}\dotNetFx40_Full_setup.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end
end
else // Vista / 2008 or later: Install .NET 4.6
Success := Exec(ExpandConstant('{tmp}\NDP46-KB3045560-Web.exe'), '/passive', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Check for errors
if not Success or ((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_SUCCESS_REBOOT_REQUIRED)) then
RaiseException('Failed to install .NET: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
Result := ResultCode = ERROR_SUCCESS_REBOOT_REQUIRED;
end;
IIS Installation / Uninstall
Installs IIS with ASP.NET 4. Will activate the same (default-)features as if it was activated via GUI.
After installation is completed, ASP.NET is (re-)registered with IIS.
Note: For Win XP/2003, the "Windows Installation CD" is required.
// Returns True if a restart is required. Throws exceptions.
function InstallIIs(): Boolean;
var
Version: TWindowsVersion;
Success: Boolean;
ResultCode: Integer;
IIS56IniFile: String;
begin
GetWindowsVersionEx(Version);
if (Version.Major <= 5) then // XP / 2003: Use "sysocmgr"
begin
IIS56IniFile := ExpandConstant('{tmp}\~iis56.ini');
SaveStringToFile(IIS56IniFile, '[Components]' + #13#10 + 'iis_common = ON' + #13#10 + 'iis_www = ON' + #13#10 + 'iis_inetmgr = ON', False);
Success := Exec('sysocmgr', ExpandConstant('/i:"{win}\inf\sysoc.inf" /u:"' + IIS56IniFile + '"'), '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
DeleteFile(IIS56IniFile);
end
else if (Version.Major = 6) and (Version.Minor = 0) then // Vista / 2008: Use "pkgmgr"
begin
Success := Exec('pkgmgr', '/iu:' +
// Enables everything a fresh install would also (implicitly) enable.
// This is important in case IIS was already installed and some features manually disabled.
'WAS-WindowsActivationService;WAS-ProcessModel;WAS-NetFxEnvironment;WAS-ConfigurationAPI' +
';IIS-WebServerRole' +
';IIS-WebServerManagementTools;IIS-ManagementConsole' +
';IIS-WebServer' +
';IIS-ApplicationDevelopment;IIS-NetFxExtensibility;IIS-ASPNET;IIS-ISAPIExtensions;IIS-ISAPIFilter' +
';IIS-CommonHttpFeatures;IIS-HttpErrors;IIS-DefaultDocument;IIS-StaticContent;IIS-DirectoryBrowsing' +
';IIS-Performance;IIS-HttpCompressionStatic' +
';IIS-Security;IIS-RequestFiltering' +
';IIS-HealthAndDiagnostics;IIS-RequestMonitor;IIS-HttpLogging',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end
else if (Version.Major = 6) and (Version.Minor = 1) then // 7 / 2008 R2 / 2011: Use "Dism"
begin
Success := Exec('Dism', '/Online /Enable-Feature' +
// Enables everything a fresh install would also (implicitly) enable.
// This is important in case IIS was already installed and some features manually disabled.
// "Parent fetaures" are NOT automatically enabled by "Dism".
' /FeatureName:WAS-WindowsActivationService /FeatureName:WAS-ProcessModel /FeatureName:WAS-NetFxEnvironment /FeatureName:WAS-ConfigurationAPI' +
' /FeatureName:IIS-WebServerRole' +
' /FeatureName:IIS-WebServerManagementTools /FeatureName:IIS-ManagementConsole' +
' /FeatureName:IIS-WebServer' +
' /FeatureName:IIS-ApplicationDevelopment /FeatureName:IIS-NetFxExtensibility /FeatureName:IIS-ASPNET /FeatureName:IIS-ISAPIExtensions /FeatureName:IIS-ISAPIFilter' +
' /FeatureName:IIS-CommonHttpFeatures /FeatureName:IIS-HttpErrors /FeatureName:IIS-DefaultDocument /FeatureName:IIS-StaticContent /FeatureName:IIS-DirectoryBrowsing' +
' /FeatureName:IIS-Performance /FeatureName:IIS-HttpCompressionStatic' +
' /FeatureName:IIS-Security /FeatureName:IIS-RequestFiltering' +
' /FeatureName:IIS-HealthAndDiagnostics /FeatureName:IIS-RequestMonitor /FeatureName:IIS-HttpLogging',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end
else // 8 / 2012 and later: Use "Dism"
begin
Success := Exec('Dism', '/Online /Enable-Feature' +
// Enables everything a fresh install would also (implicitly) enable.
// This is important in case IIS was already installed and some features manually disabled.
' /FeatureName:WAS-WindowsActivationService /FeatureName:WAS-ProcessModel /FeatureName:WAS-NetFxEnvironment /FeatureName:WAS-ConfigurationAPI' +
' /FeatureName:IIS-ManagementConsole' +
' /FeatureName:IIS-HttpErrors /FeatureName:IIS-DefaultDocument /FeatureName:IIS-StaticContent /FeatureName:IIS-DirectoryBrowsing' +
' /FeatureName:IIS-ASPNET45' +
' /FeatureName:IIS-HttpCompressionStatic' +
' /FeatureName:IIS-RequestFiltering' +
' /FeatureName:IIS-HttpLogging' +
' /All', // Implicitly enables dependent features
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
if not Success or ((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_SUCCESS_REBOOT_REQUIRED)) then
RaiseException('Cannot install IIS: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
Result := ResultCode = ERROR_SUCCESS_REBOOT_REQUIRED;
// Register ASP.NET 4 with IIS. This is required since .NET 4 was probably installed before IIS.
// This will NOT change existing web-sites (which might be using other ASP.NET versions already)
if not Exec(ExpandConstant('{dotnet40}') + '\aspnet_regiis.exe', '-iru -enable', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or
(ResultCode <> ERROR_SUCCESS) then
RaiseException('Cannot register ASP.NET: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
end;
// Returns True if a restart is required. Throws exceptions.
function UninstallIIs(): Boolean;
var
Version: TWindowsVersion;
Success: Boolean;
ResultCode: Integer;
IIS56IniFile: String;
begin
GetWindowsVersionEx(Version);
if (Version.Major <= 5) then // XP / 2003: Use "sysocmgr"
begin
IIS56IniFile := ExpandConstant('{tmp}\~iis56.ini');
SaveStringToFile(IIS56IniFile, '[Components]' + #13#10 + 'iis_common = OFF' + #13#10 + 'iis_www = OFF' + #13#10 + 'iis_inetmgr = OFF', False);
Success := Exec('sysocmgr', ExpandConstant('/i:"{win}\inf\sysoc.inf" /u:"' + IIS56IniFile + '"'), '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
DeleteFile(IIS56IniFile);
end
else if (Version.Major = 6) and (Version.Minor = 0) then // Vista / 2008: Use "pkgmgr"
Success := Exec('pkgmgr', '/norestart /uu:IIS-WebServerRole', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
else // 7 / 2008 R2 and later: Use "Dism"
Success := Exec('Dism', '/NoRestart /Online /Disable-Feature /FeatureName:IIS-WebServerRole', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if not Success or ((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_SUCCESS_REBOOT_REQUIRED)) then
RaiseException('Cannot uninstall IIS: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
Result := ResultCode = ERROR_SUCCESS_REBOOT_REQUIRED;
end;
Web-Site/App Registration and Removal
Creates a virtual directory and an ASP.NET 4 Application Pool.
Note: IIsServerNumber is the actual Web-Site which usually defaults to 1 (=> "Default Web Site").
Usage:
RegisterAppAtIIs('MyAppName', 1, 'MyAppAppPoolName');
DeleteAppFromIIs('MyAppName', 1, 'MyAppAppPoolName');
// Throws exceptions.
procedure RegisterAppAtIIs(const IIsAppName: String; const IIsServerNumber: Integer; const IIsApplicationPoolName: String);
var
Version: TWindowsVersion;
// IIS 5.1 - 6.0
IIS, WebService, WebSite, WebRoot, vDir, AppPools, AppPool: Variant;
// IIS 7 and later
ResultCode: Integer;
ExecResult: String;
WebSiteName: String;
begin
GetWindowsVersionEx(Version);
if (Version.Major = 5) and (Version.Minor <= 2) then // XP, 2003: IIS 5.1 - 6.0
begin
try
// Create the main IIS COM Automation object
IIS := CreateOleObject('IISNamespace');
WebService := IIS.GetObject('IIsWebService', 'localhost/W3SVC');
// Get web site
WebSite := WebService.GetObject('IIsWebServer', IntToStr(IIsServerNumber));
WebRoot := WebSite.GetObject('IIsWebVirtualDir', 'Root');
except
RaiseException(Format('Web-site #%d not found: ', [IIsServerNumber]) + GetExceptionMessage());
end;
// Delete the virtual dir if it already exists
try
WebRoot.Delete('IIsWebVirtualDir', IIsAppName);
WebRoot.SetInfo();
except
end;
if (Version.Minor = 1) then // XP: IIS 5.1
begin
// Create the virtual directory
try
vDir := WebRoot.Create('IIsWebVirtualDir', IIsAppName);
vDir.AccessRead := True;
vDir.AccessScript := True;
vDir.AppFriendlyName := IIsAppName;
vDir.Path := ExpandConstant('{app}');
vDir.AppCreate(True); // Create in "InProc" mode (don't really know why though)
vDir.SetInfo();
except
RaiseException('Cannot create virtual directory: ' + GetExceptionMessage());
end;
end
else if (Version.Minor = 2) then // 2003: IIS 6.0
begin
// Application pool stuff
AppPools := WebService.GetObject('IIsApplicationPools', 'AppPools');
try
// Check if the application pool already exists
AppPool := AppPools.GetObject('IIsApplicationPool', IIsApplicationPoolName);
except
AppPool := Null;
end;
if VarIsNull(AppPool) then
begin
// Create the application pool
try
AppPool := AppPools.Create('IIsApplicationPool', IIsApplicationPoolName);
AppPool.SetInfo();
except
RaiseException('Cannot add application pool: ' + GetExceptionMessage());
end;
end;
// Create the virtual directory
try
vDir := WebRoot.Create('IIsWebVirtualDir', IIsAppName);
vDir.AccessRead := True;
vDir.AccessScript := True;
vDir.AppFriendlyName := IIsAppName;
vDir.Path := ExpandConstant('{app}');
vDir.AppCreate(True); // Create in "InProc" mode
vDir.AppPoolId := IIsApplicationPoolName;
vDir.SetInfo();
except
RaiseException('Cannot create virtual directory: ' + GetExceptionMessage());
end;
end;
// Register handlers for ASP.NET 4 (important if other ASP.NET versions are present)
if not ExecWithResult(ExpandConstant('{dotnet40}') + '\aspnet_regiis.exe',Format('-s "W3SVC/%d/ROOT/%s"', [IIsServerNumber, IIsAppName]),'',SW_HIDE,ewWaitUntilTerminated, ResultCode, ExecResult) or (ResultCode <> ERROR_SUCCESS) then
RaiseException('Cannot set ASP.NET version: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
end
else if (Version.Major >= 6) then // Vista / 2008 or later : IIS 7 or later
begin
// Get name of web-site
if not IIs7ExecAppCmd(Format('list site /id:%d /text:name', [IIsServerNumber]), ExecResult, ResultCode) then
RaiseException(Format('Cannot get name of web-site #%d: ', [IIsServerNumber]) + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
if (Length(Trim(ExecResult)) = 0) then
RaiseException(Format('Web-site #%d not found.', [IIsServerNumber]));
WebSiteName := ExecResult;
// Delete the application if it already exists
if not IIs7ExecAppCmd(Format('delete app "%s/%s"', [WebSiteName, IIsAppName]), ExecResult, ResultCode) or
((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_NOT_FOUND) and (ResultCode <> ERROR_NOT_SUPPORTED)) then
RaiseException('Cannot delete application: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
// Check if the application pool already exists (we don't delete it, since another GVS instance might be using it too)
if not IIs7ExecAppCmd(Format('list apppool "%s" /text:name', [IIsApplicationPoolName]), ExecResult, ResultCode) or
((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_INVALID_FUNCTION)) then
RaiseException('Cannot list application pools: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
// Create the application pool
if (ExecResult <> IIsApplicationPoolName) then
begin
if not IIs7ExecAppCmd(Format('add apppool /name:"%s" /managedRuntimeVersion:v4.0', [IIsApplicationPoolName]), ExecResult, ResultCode) or (ResultCode <> ERROR_SUCCESS) then
RaiseException('Cannot add application pool: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
end;
// Create the application
if not IIs7ExecAppCmd(Format('add app /site.name:"%s" /path:"/%s" /physicalPath:"%s" /applicationPool:"%s"', [WebSiteName, IIsAppName, ExpandConstant('{app}'), IIsApplicationPoolName]), ExecResult, ResultCode) or
(ResultCode <> ERROR_SUCCESS) then
RaiseException('Cannot add application: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
end
else
RaiseException('Cannot register web-site: Unknown OS version.');
end;
// Throws exceptions.
procedure DeleteAppFromIIs(const IIsAppName: String; const IIsServerNumber: Integer; const IIsApplicationPoolName: String);
var
Version: TWindowsVersion;
// IIS 5.1 - 6.0
IIS, WebService, WebSite, WebRoot, AppPools: Variant;
// IIS 7 and later
ResultCode: Integer;
ExecResult: String;
WebSiteName: String;
begin
GetWindowsVersionEx(Version);
if (Version.Major = 5) and (Version.Minor <= 2) then // XP, 2003: IIS 5.1 - 6.0
begin
try
// Create the main IIS COM Automation object
IIS := CreateOleObject('IISNamespace');
WebService := IIS.GetObject('IIsWebService', 'localhost/W3SVC');
// Get web site
WebSite := WebService.GetObject('IIsWebServer', IntToStr(IIsServerNumber));
WebRoot := WebSite.GetObject('IIsWebVirtualDir', 'Root');
except
RaiseException(Format('Web-site #%d not found: ', [IIsServerNumber]) + GetExceptionMessage());
end;
// Delete the virtual dir
try
WebRoot.Delete('IIsWebVirtualDir', IIsAppName);
WebRoot.SetInfo();
except
end;
if (Version.Minor = 2) then // 2003: IIS 6.0
begin
// Application pool stuff
AppPools := WebService.GetObject('IIsApplicationPools', 'AppPools');
try
// Delete the application pool
AppPools.Delete('IIsApplicationPool', IIsApplicationPoolName);
except
end;
end;
end
else if (Version.Major >= 6) then // Vista / 2008 or later : IIS 7 or later
begin
// Get name of web-site
if not IIs7ExecAppCmd(Format('list site /id:%d /text:name', [IIsServerNumber]), ExecResult, ResultCode) then
RaiseException(Format('Cannot get name of web-site #%d: ', [IIsServerNumber]) + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
if (Length(Trim(ExecResult)) = 0) then
RaiseException(Format('Web-site #%d not found.', [IIsServerNumber]));
WebSiteName := ExecResult;
// Delete the application
if not IIs7ExecAppCmd(Format('delete app "%s/%s"', [WebSiteName, IIsAppName]), ExecResult, ResultCode) or
((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_NOT_FOUND) and (ResultCode <> ERROR_NOT_SUPPORTED)) then
RaiseException('Cannot delete application: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
// Delete the application pool
if not IIs7ExecAppCmd(Format('delete apppool "%s"', [IIsApplicationPoolName]), ExecResult, ResultCode) or
((ResultCode <> ERROR_SUCCESS) and (ResultCode <> ERROR_NOT_FOUND)) then
RaiseException('Cannot delete application pool: ' + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
end
else
RaiseException('Cannot delete web-site: Unknown OS version.');
end;
Useful Helpers
Finds the Windows user-account used by ASP.NET to read/write files in the virtual directory. Useful to set permissions for "App_Data".
// Throws exceptions.
function GetIIsAppPoolIdentity(const IIsApplicationPoolName: String): String;
var
Version: TWindowsVersion;
ResultCode: Integer;
ExecResult: String;
IIS, AppPool: Variant;
begin
GetWindowsVersionEx(Version);
if (Version.Major = 5) and (Version.Minor = 1) then // XP: IIS 5.1
begin
// TODO: Should be read from "[.NET 4]\Config\machine.config" -> system.web.processModel.userName
// - "System"
// - "Machine" -> "ASPNET" (default)
// - A custom user account
// See https://msdn.microsoft.com/en-us/library/dwc1xthy.aspx
Result := 'ASPNET';
end
else if (Version.Major = 5) and (Version.Minor = 2) then // 2003: IIS 6.0
begin
try
IIS := CreateOleObject('IISNamespace');
AppPool := IIS.GetObject('IIsApplicationPool', 'localhost/W3SVC/AppPools/' + IIsApplicationPoolName);
if (AppPool.AppPoolIdentityType = 0) then
Result := 'NT AUTHORITY\SYSTEM'
else if (AppPool.AppPoolIdentityType = 1) then
Result := 'NT AUTHORITY\LOCAL SERVICE'
else if (AppPool.AppPoolIdentityType = 2) then
Result := 'NT AUTHORITY\NETWORKSERVICE'
else if (AppPool.AppPoolIdentityType = 3) then
Result := AppPool.WAMUserName
else
RaiseException('Cannot get application pool identity: Unknown identity type (' + IntToStr(AppPool.AppPoolIdentityType) + ')');
except
RaiseException('Cannot get application pool identity: Unable to get object');
end;
end
else if (Version.Major >= 6) then // Vista / 2008 or later
begin
if not IIs7ExecAppCmd(Format('list apppool "%s" /text:processModel.identityType', [IIsApplicationPoolName]), ExecResult, ResultCode) or
(ResultCode <> ERROR_SUCCESS) then
RaiseException('Cannot get application pool identity.');
if (ExecResult = 'LocalSystem') then
Result := 'NT AUTHORITY\SYSTEM'
else if (ExecResult = 'LocalService') then
Result := 'NT AUTHORITY\LOCAL SERVICE'
else if (ExecResult = 'NetworkService') then
Result := 'NT AUTHORITY\NETWORKSERVICE'
else if (ExecResult = 'ApplicationPoolIdentity') then
Result := 'IIS AppPool\' + IIsApplicationPoolName
else
RaiseException('Cannot get application pool identity: Unknown identity type "' + ExecResult + '"');
end
else
RaiseException('Cannot get application pool identity: Unknown OS version: ' + IntToStr(Version.Major) + '.' + IntToStr(Version.Minor));
end;
Returns the physical path to a Web-Site. Useful to find the correct place to install files to.
// Throws exceptions.
function IIsGetPhysicalPath(const IIsServerNumber: Integer): String;
var
Version: TWindowsVersion;
IIS, WebService, WebSite, WebRoot: Variant;
ResultCode: Integer;
ExecResult: String;
WebSiteName: String;
PhysPath: String;
begin
GetWindowsVersionEx(Version);
if (Version.Major = 5) then
begin
try
// Create the main IIS COM Automation object
IIS := CreateOleObject('IISNamespace');
WebService := IIS.GetObject('IIsWebService', 'localhost/W3SVC');
// Get web site
WebSite := WebService.GetObject('IIsWebServer', IntToStr(IIsServerNumber));
WebRoot := WebSite.GetObject('IIsWebVirtualDir', 'Root');
except
RaiseException(Format('Web-site #%d not found: ', [IIsServerNumber]) + GetExceptionMessage());
end;
PhysPath := WebRoot.Path;
end
else if (Version.Major >= 6) then // Vista / 2008 or later
begin
// Get name of web-site
if not IIs7ExecAppCmd(Format('list site /id:%d /text:name', [IIsServerNumber]), ExecResult, ResultCode) then
RaiseException(Format('Cannot get name of web-site #%d: ', [IIsServerNumber]) + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
if (Length(Trim(ExecResult)) = 0) then
RaiseException(Format('Web-site #%d not found.', [IIsServerNumber]));
WebSiteName := ExecResult;
// Get physical path
if not IIs7ExecAppCmd(Format('list vdir /app.name:"%s"/ /text:physicalPath', [WebSiteName]), ExecResult, ResultCode) or
(ResultCode <> ERROR_SUCCESS) then
RaiseException(Format('Cannot get physical path to web-site "%s": ', [WebSiteName]) + SysErrorMessage(ResultCode) + ' (' + IntToStr(ResultCode) + ')');
PhysPath := ExecResult;
end
else
RaiseException('Cannot get physical path to web-site root: Unknown OS version: ' + IntToStr(Version.Major) + '.' + IntToStr(Version.Minor));
// Expand environment variables in path (e.g. "%SystemDrive%")
Result := ExpandEnvVars(PhysPath);
end;

So, in the end what I did was create a separate executable coded in C# which will install IIS through the command line before installation and will then register IIS with the appropriate application pool etc after installation. I then proceeded to call the executable through Inno Setup in PrepareToInstall to install IIS, and in CurStepChanged to register IIS.

we are using also IIS and thinking about to automate the IIS installation. As far as I know is it possible to do all the stuff via command line tools. So you can create your own Inno Custom Pages to retrieve the necessary data from the user. (You can use the Inno Setup Form Designer for this). Once you've collected all your stuff, you can create and then fill your prepared template batch files and in the post install the batches will be executed.
I would try it like this:
try to do everything with a batch file concerning your IIS installation (Take a look at this archived page
when you can do anything with batch files for the IIS Configuration, then you need to make template batch files for the Inno setup
create custom pages for Inno to retrieve the necessary data
in Inno, replace the template batches with the variables
execute the batches in Inno
Hope this helps

Related

How to download a file with resume support using Indy?

I have already tried code from StackOverflow, f.e. Download, pause and resume an download using Indy components.
But when I try to use IdHttp1.Disconnect() in order to pause download and then resume it, the download does not resume. The file is downloaded from zero. So, what should I do in order to add resume support to my Delphi app?
BTW: the server allows download resuming, because IDM can resume the download.
You have to check the size of the remote file, and the size of the local file (if exists).
If the size of the remote file is larger than the size of the local file, then you have to set the Ranges property of the Request property of TIdHTTP to the starting point (the local file size).
For example, if the remote file size is 100000 bytes, and the local file size is 80000 bytes, then you have to set http.Request.Ranges to start at 80000.
Here is a fully working code for you to use:
function FileSize(const FileName: string): Int64;
var
AttributeData: TWin32FileAttributeData;
begin
if GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, #AttributeData) then
begin
Int64Rec(Result).Lo := AttributeData.nFileSizeLow;
Int64Rec(Result).Hi := AttributeData.nFileSizeHigh;
end
else
Result := -1;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
http: TIdHTTP;
FS: TFileStream;
file_url: String;
file_path: String;
web_file_size: Integer;
local_file_size: Integer;
IhaveToDownload: Boolean;
begin
file_url := 'http://212.183.159.230/50MB.zip';
file_path := 'C:\50MB.zip';
IhaveToDownload := False;
FS := nil;
http := TIdHTTP.Create(nil);
try
//I will check if local file exists
if FileExists(file_path) then
begin
//I have to run http.Head() to get the ContentLength of the remote file.
http.Head(file_url);
//web_file_size now has the remote file size in bytes
web_file_size := http.Response.ContentLength;
//local_file_size now has the local file size in bytes
local_file_size := FileSize(file_path);
//check if remote file is bigger than local file (so I have to resume download)
if (web_file_size > local_file_size) and (http.Response.AcceptRanges = 'bytes') then
begin
//remote file is bigger than local file, so I will resume download.
http.Request.Ranges.Add.StartPos := local_file_size;
FS := TFileStream.Create(file_path, fmOpenReadWrite);
FS.Position := local_file_size;
IhaveToDownload := True;
end
else if web_file_size <> local_file_size then
begin
//remote file is not equal with local file, so I will restart download.
FS := TFileStream.Create(file_path, fmCreate);
IhaveToDownload := True;
end;
end
else
begin
//File does not exist locally. I will create a new local file
FS := TFileStream.Create(file_path, fmCreate);
IhaveToDownload := True;
end;
if IhaveToDownload then
http.Get(file_url, FS);
finally
FS.Free;
http.Free;
end;
end;

Check if .NET 5.0 is installed in Inno Setup

I have the following .iss script to compile a games launcher I'm working on that uses .NET 5.0. Currently it tries to install .NET 5.0 from the installer it has every time instead of checking if its required first. I've found plenty of resources that tell you how to do it for the .NET Framework but hardly anything for .NET 5.0 which is a updated version of .NET Core. How do I check if .NET 5.0 is already installed before trying to install it anyway?
I also am aware that 5.0 is nearer end of life but I'm using Visual Studio 2019 which 6.0 isn't compatible with and would prefer not to have to use any work arounds to get 2019 to play ball with it.
#define AppName "LowPoly Games Launcher"
#define AppEXEName "LPG Launcher.exe"
[Setup]
AppName={#AppName}
[Files]
Source: "..\bin\Release\net5.0-windows\*"; DestDir: "{app}"; \
Flags: ignoreversion recursesubdirs;
Source: "Resources\windowsdesktop-runtime-5.0.17-win-x64.exe"; \
DestDir: "{app}"; Flags: ignoreversion deleteafterinstall
[Run]
Filename: "{app}\{#AppEXEName}"; \
Description: "{cm:LaunchProgram, {#StringChange(AppName, '&', '&&')}}"; \
Flags: nowait postinstall
Filename: "{app}\windowsdesktop-runtime-5.0.17-win-x64.exe"; \
Parameters: "/q/passive"; Flags: waituntilterminated; \
StatusMsg: Microsoft .NET Framework 5.0 is being installed. Please wait...
Based on:
How to get an output of an Exec'ed program in Inno Setup?
How to check that .NET is already installed
You can do:
[Code]
function IsDotNetInstalled(DotNetName: string): Boolean;
var
Cmd, Args: string;
FileName: string;
Output: AnsiString;
Command: string;
ResultCode: Integer;
begin
FileName := ExpandConstant('{tmp}\dotnet.txt');
Cmd := ExpandConstant('{cmd}');
Command := 'dotnet --list-runtimes';
Args := '/C ' + Command + ' > "' + FileName + '" 2>&1';
if Exec(Cmd, Args, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
(ResultCode = 0) then
begin
if LoadStringFromFile(FileName, Output) then
begin
if Pos(DotNetName, Output) > 0 then
begin
Log('"' + DotNetName + '" found in output of "' + Command + '"');
Result := True;
end
else
begin
Log('"' + DotNetName + '" not found in output of "' + Command + '"');
Result := False;
end;
end
else
begin
Log('Failed to read output of "' + Command + '"');
end;
end
else
begin
Log('Failed to execute "' + Command + '"');
Result := False;
end;
DeleteFile(FileName);
end;
And it use it like:
if IsDotNetInstalled('Microsoft.NETCore.App 5.0.') then // ...

Starting .NET Core installer using Inno Setup fails with result code 2

I'm trying to build up an installer using Inno Setup. I need to setup a prerequisite installation for .NET Core framework. By far I'm able to detect .NET Core existence. But I'm having difficulty with executing the installation.
Here's the code I have by far:
[Files]
Source: "\\shared\path\dotnet-runtime-3.1.10-win-x64.exe"; DestDir: "{tmp}"; \
Flags: deleteafterinstall; BeforeInstall: InstallFramework; \
Check: FrameworkIsNotInstalled
[Code]
var
CancelWithoutPrompt: boolean;
InstallValue: Cardinal;
function InitializeSetup(): Boolean;
begin
CancelWithoutPrompt := false;
result := true;
end;
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
if CurPageID=wpInstalling then
Confirm := not CancelWithoutPrompt;
end;
function FrameworkIsNotInstalled: Boolean;
begin
Result := not RegQueryDWordValue(HKEY_LOCAL_MACHINE,
'SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.NETCore.App',
'3.1.10', InstallValue) or (InstallValue <> 1)
end;
procedure InstallFramework;
var
StatusText: string;
ResultCode: Integer;
begin
StatusText := WizardForm.StatusLabel.Caption;
WizardForm.StatusLabel.Caption := 'Installing .net core framework...';
WizardForm.ProgressGauge.Style := npbstMarquee;
try
if not Exec(ExpandConstant('{tmp}\dotnet-runtime-3.1.10-win-x64.exe'), '/q /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
begin
// you can interact with the user that the installation failed
MsgBox('.NET Core installation failed with code: ' + IntToStr(ResultCode) + '.',
mbError, MB_OK);
CancelWithoutPrompt := true;
WizardForm.Close;
end;
finally
WizardForm.StatusLabel.Caption := StatusText;
WizardForm.ProgressGauge.Style := npbstNormal;
end;
end;
I'm getting result code 2 response from the installation, anyone could enlighten me what's code 2 error about? Or is that any way I could extract further detail on the error information? I tried empty argument and tried to search for Code 2 error information on the internet but still no luck.
I had tried with Shellexec and both return the same result
The installation directly using the .net exe are working fine locally
Any help or information would be much appreciate =)
You are trying to run the dotnet-runtime-3.1.10-win-x64.exe before you actually "install" it to the {tmp}:
BeforeInstall: InstallFramework
You have to use the AfterInstall parameter:
AfterInstall: InstallFramework

lua with Nginx : bad argument to 'getNewSessionID' (string expected, got nil)

I have compiled nginx with Lua. and i am facing a problem .
When i hit localhost i get an error in nginx error logs
2015/05/29 07:41:32 [error] 112#0: *1 lua entry thread aborted: runtime error: /opt/hello.lua:11: bad argument #1 to 'getNewSessionID' (string expected, got nil)
stack traceback:
coroutine 0:
[C]: in function 'getNewSessionID'
Nginx configurations are
location / {
default_type 'text/html';
content_by_lua_file /opt/hello.lua;
}
And my hello.lua look like
package.path = package.path ..';/opt/?.lua'
JSON = (loadfile "/opt/JSON.lua")()
local proxyFunctions = require("proxyCode")
coherenceNGIXLocation = "/coherence"
local coherenceCookie = proxyFunctions.getExistingSessionID()
-- To Create a coherance Session if not present
if (not coherenceCookie or coherenceCookie == '') then
-- ngx.log(ngx.INFO,"No Coherence Session Exists, Creating New")
local uidCookieValueEncoded = proxyFunctions.base64Encode(str,proxyFunctions.getNewSessionID())
local sessCreateResponse = proxyFunctions.coherencePut('{"sessionToken":"'..uidCookieValueEncoded..'"}')
local parsedJSON= JSON:decode(sessCreateResponse.body)
ngx.log(ngx.INFO, "Coherence Session Create")
ngx.log(ngx.INFO, "\n\n\n=============Coherence Res==============\n\n\n"..sessCreateResponse.body.."\n\n\n=============Coherence Res==============\n\n\n")
end
local client_cookie = ngx.req.get_headers()["cookie"]
-- To Create a coherance Session if not present
ngx.log(ngx.DEBUG, "\n\n\n=============REQ==============\n\n\n"..proxyFunctions.deepPrint(ngx.req.get_headers()).."\n\n\n=============REQ==============\n\n\n")
-- Set the cookie for the legacy request
ngx.req.set_header('cookie', client_cookie)
ngx.log(ngx.INFO, "\n\n\n=============REQ URI==============\n\n\n"..ngx.var.request_uri.."\n\n\n=============REQ URI==============\n\n\n")
local legacy_res = ngx.location.capture("/app"..ngx.var.request_uri, { share_all_vars = true })
ngx.log(ngx.DEBUG, "\n\n\n=============APP Res==============\n\n\n"..proxyFunctions.deepPrint(legacy_res.header).."\n\n\n=============APP Res==============\n\n\n")
if (legacy_res.status == 302) then
ngx.redirect(legacy_res.header.Location )
else
ngx.log(ngx.INFO, "Passing the Page/Layout to Maken")
local dev_res_encoded = legacy_res.body
dev_res_encoded = ngx.escape_uri(dev_res_encoded)
-- ngx.req.set_header('Cookie', ngx.req.get_headers()["cookie"])
-- ngx.req.set_header("Content-Type", "text/html") -- application/x-www-form-urlencoded -- 'page='..dev_res_encoded
ngx.req.set_header('cookie', client_cookie) -- maken/render
-- legacy_res.header('Content-Length', legacy_res.header['Content-Length']);
local maken_res = ngx.location.capture("/maken/", {method = ngx.HTTP_POST, body = legacy_res.body, share_all_vars = true} )
ngx.header['Set-Cookie'] = legacy_res.header["Set-Cookie"]
ngx.log(ngx.INFO, "Sending Back Maken Response to Client/Browser with Cookies from lagacy")
ngx.say(maken_res.body)
end
My proxyCode.lua looks like
function _M.getNewSessionID()
-- strip the sessionID length 11 cookie name before putting into the session
-- return string.sub(ngx.var.uid_set,11)
return _M.removeCookieSessionPrefixname(ngx.var.uid_set,11)
end
function _M.removeCookieSessionPrefixname(cookieName)
return string.sub(cookieName,11)
end
function _M.getIncomingAuthToken()
local cookie = ngx.req.get_headers()["Cookie"]
if (cookie ~=nil) then
local str = string.match(cookie,"AuthToken=(.*)$")
if (str ~= nil) then
return string.sub(str,11)
end
end
return nil
end
Can someone help me why i am getting this error ? i am new to Lua programming. and trying to run legacy application on my laptop.

How to get name of ODBC driver's DLL file for a given ODBC driver

How do I programatically get the name of an ODBC driver's DLL file for a given ODBC driver. For example, given "SQL Server Native Client 10.0" I want to find the name of that driver's DLL file: sqlncli10.dll. I can see this in REGEDIT in the "Driver" entry in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI. If I try to read the value from the registry in my code it returns an empty string. I also tried using the ODBC API function SQLDrivers. The code below successfully returns all the values of the attributes in the Attribs variable except "Driver". Everything is there - APILevel, ConnectFunctions, CPTimeout, etc - but "Driver" is not in the list.
repeat
Status := SQLDrivers (HENV, SQL_FETCH_NEXT, PAnsiChar(DriverName), 255,
NameLen, PAnsiChar(Attribs), 1024, AttrLen);
if Status = 0 then begin
List.Add(DriverName);
List.Add(Attribs);
end;
until Status <> 0;
You can use SQLGetInfo() with InfoType=SQL_DRIVER_NAME
I hope this will look like:
Status := SQLGetInfo(ConnEnv, SQL_DRIVER_NAME, PAnsiChar(DriverName), 255, NameLen);
But this function works with already connected database.
I tried SQLDrives() and you are right: in my environment this function also do not return DLL name. So I tried to read it from registry and it worked this way:
DLLName := RegGetStringDirect(HKEY_LOCAL_MACHINE, 'SOFTWARE\ODBC\ODBCINST.INI\' + DriverName, 'Driver');
For driver: IBM INFORMIX ODBC DRIVER I got: C:\informix\bin\iclit09b.dll
For driver: SQL Server I got: C:\WINDOWS\system32\SQLSRV32.dll
RegGetStringDirect() is my function based on Windows API to read something from registry.
EDIT:
Two functions to read "SQL Server" ODBC driver dll name by Ron Schuster moved from comment:
procedure TForm1.Button1Click(Sender: TObject);
//using Windows API calls
var
KeyName, ValueName, Value: string;
Key: HKEY;
ValueSize: Integer;
begin
ValueName := 'Driver';
KeyName := 'SOFTWARE\ODBC\ODBCINST.INI\SQL Native Client';
if RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar(KeyName), 0, KEY_READ, Key) = 0 then
if RegQueryValueEx(Key, PChar(ValueName), nil, nil, nil, #ValueSize) = 0 then begin
SetLength(Value, ValueSize);
RegQueryValueEx(Key, PChar(ValueName), nil, nil, PByte(Value), #ValueSize);
ShowMessage(Value);
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
//using TRegistry class
var
KeyName, ValueName, Value: string;
Reg: TRegistry;
begin
ValueName := 'Driver';
KeyName := 'SOFTWARE\ODBC\ODBCINST.INI\SQL Native Client';
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKeyReadOnly(KeyName) then begin
Value := Reg.ReadString(ValueName);
ShowMessage(Value);
end;
finally
Reg.Free;
end;
end;

Resources