; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define PublishPath "F:\source\iWS5\IWS5.Server\bin\Release\net8.0\publish" #define DeploymentPath "F:\source\setup-packages\IWS5.Server" #define MyAppName "IWS5.Server" #define MyAppExeName "IWS5.Server.exe" #define MyAppExe AddBackslash(PublishPath) + MyAppExeName #define MyAppPublisher "Greenfield Investments" ; Extract version string from PowerShell script output and combine with EXE version string #define MyAppVersion GetVersionNumbersString(MyAppExe) + "_" + ExecAndGetFirstLine("powershell.exe", "-NoProfile -ExecutionPolicy Bypass -File """ + AddBackslash(DeploymentPath) + "Generate_Version.ps1"" """ + MyAppName + """ """ + PublishPath + """", AddBackslash(DeploymentPath)) [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{7CA4C9E9-22B5-4612-86A7-63A6E26B2AD5} AppName={#MyAppName} ;AppVersion={#MyAppVersion} AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} DefaultDirName={autopf}\{#MyAppPublisher}\{#MyAppName} DefaultGroupName={#MyAppName} DisableProgramGroupPage=yes ; Uncomment the following line to run in non administrative install mode (install for current user only). ;PrivilegesRequired=lowest OutputDir={#DeploymentPath} OutputBaseFilename={#MyAppName}_{#MyAppVersion}_Setup SolidCompression=yes WizardStyle=modern ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" [Dirs] Name: "C:\Logs\IWS5_Server"; Flags: uninsneveruninstall [Files] Source: "{#AddBackslash(PublishPath)}*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Run] Filename: "{src}\redist\aspnetcore-runtime-8.0.21-win-x64.exe"; Parameters: "/passive"; StatusMsg: "Installing the ASP.NET Core 8.0 runtime..."; Flags: runascurrentuser Filename: "{src}\redist\dotnet-runtime-8.0.21-win-x64.exe"; Parameters: "/passive"; Description: "Installing the .NET 8.0 runtime..."; Flags: runascurrentuser [Icons] Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Code] var DatabaseConnectionPage: TInputQueryWizardPage; DatabaseConnectionSucceeded: Boolean; ProcessAgentConfigurationPage: TInputQueryWizardPage; // Import ADODB COM object for SQL Server connection testing const adUseClient = 3; // Function to replace part of a string between quotes // Used for *.json files function ReplaceJSONQuotedValue(const Line: String; const SearchKey: String; const NewValue: String): String; var PosKey, PosColon, PosQuote1, PosQuote2: Integer; begin Result := Line; PosKey := Pos(SearchKey, Line); if PosKey = 0 then Exit; PosColon := Pos(':', Copy(Line, PosKey, Length(Line))); if PosColon = 0 then Exit; PosColon := PosKey + PosColon - 1; PosQuote1 := Pos('"', Copy(Line, PosColon, Length(Line))); if PosQuote1 = 0 then Exit; PosQuote1 := PosColon + PosQuote1 - 1; PosQuote2 := Pos('"', Copy(Line, PosQuote1 + 1, Length(Line))); if PosQuote2 = 0 then Exit; PosQuote2 := PosQuote1 + PosQuote2; Result := Copy(Line, 1, PosQuote1) + NewValue + Copy(Line, PosQuote2, Length(Line)); end; // Procedure to find and replace string in file procedure ReplaceStringInFile(const FileName: String; const SearchKey: String; const NewValue: String); var Lines: TArrayOfString; i: Integer; Modified: Boolean; begin Modified := False; // Load file into array if LoadStringsFromFile(FileName, Lines) then begin // Process each line for i := 0 to GetArrayLength(Lines) - 1 do begin if Pos(SearchKey, Lines[i]) > 0 then begin Lines[i] := ReplaceJSONQuotedValue(Lines[i], SearchKey, NewValue); Modified := True; end; end; // Save modified content back to file if Modified then SaveStringsToFile(FileName, Lines, False); end; end; // Make SQL Server connection using ADO // Allows insertion function MakeSQLConnection(const ConnectionString: String; var ErrorMsg: String; const InsertStatement: String): Boolean; var ADOConnection: Variant; ADOConnectionString: String; begin Result := False; ErrorMsg := ''; try ADOConnection := CreateOleObject('ADODB.Connection'); try // Use default SQLOLEDB provider ADOConnectionString := 'Provider=SQLOLEDB;' + ConnectionString; ADOConnection.ConnectionTimeout := 10; ADOConnection.Open(ADOConnectionString); // Execute insert statement if it exists if InsertStatement <> '' then begin ADOConnection.Execute(InsertStatement); end; Result := True; ADOConnection.Close; except ErrorMsg := GetExceptionMessage; end; except ErrorMsg := 'Failed to create ADODB Connection object for SQL Server. Please ensure MDAC is installed.'; end; end; // Build database connection string from individual fields function BuildDatabaseConnectionString(): String; var ConnStr: String; begin ConnStr := 'Data Source=' + DatabaseConnectionPage.Values[0] + ';'; ConnStr := ConnStr + 'Initial Catalog=' + DatabaseConnectionPage.Values[1] + ';'; ConnStr := ConnStr + 'User ID=' + DatabaseConnectionPage.Values[2] + ';'; ConnStr := ConnStr + 'Password=' + DatabaseConnectionPage.Values[3] + ';'; ConnStr := ConnStr + 'Encrypt=True;'; ConnStr := ConnStr + 'Trust Server Certificate=True;'; Result := ConnStr; end; // Returns whether the database connection succeeded function ConfirmDatabaseConnection(): Boolean; var DatabaseConnectionString: String; ErrorMsg: String; begin Result := False; // Validate required fields if Trim(DatabaseConnectionPage.Values[0]) = '' then begin MsgBox('Please enter a value for: Server name (Data Source).', mbError, MB_OK); Exit; end; if Trim(DatabaseConnectionPage.Values[1]) = '' then begin MsgBox('Please enter a value for: Initial Catalog (Database).', mbError, MB_OK); Exit; end; if Trim(DatabaseConnectionPage.Values[2]) = '' then begin MsgBox('Please enter a value for: User ID (Uses SQL Server Authentication).', mbError, MB_OK); Exit; end; if Trim(DatabaseConnectionPage.Values[3]) = '' then begin MsgBox('Please enter a value for: Password.', mbError, MB_OK); Exit; end; // Build connection string from fields DatabaseConnectionString := BuildDatabaseConnectionString(); // Show a progress indicator WizardForm.StatusLabel.Caption := 'Testing database connection...'; WizardForm.ProgressGauge.Style := npbstMarquee; try if MakeSQLConnection(DatabaseConnectionString, ErrorMsg, '') then begin MsgBox('Connection successful!' + #13#10#13#10 + 'The database connection was established successfully.', mbInformation, MB_OK); Result := True; end else begin MsgBox('Connection failed!' + #13#10#13#10 + 'Error details:' + #13#10 + ErrorMsg + #13#10#13#10 + 'Please verify your connection details and ensure:' + #13#10 + '- The SQL Server is running and accessible' + #13#10 + '- The server name and database are correct' + #13#10 + '- Your credentials are valid (if using SQL Auth)' + #13#10 + '- Firewall settings allow the connection', mbError, MB_OK); end; finally WizardForm.StatusLabel.Caption := ''; WizardForm.ProgressGauge.Style := npbstNormal; end; end; // Initialize the custom input page procedure InitializeWizard(); var DatabaseConnectionPageDescriptionText: String; begin // Create a custom page for database connection configuration DatabaseConnectionPageDescriptionText := 'Please specify the database connection details for your Process Director database server.'; if (ExpandConstant('{param:BypassDatabaseConnection|false}') = 'false') then begin DatabaseConnectionPageDescriptionText := DatabaseConnectionPageDescriptionText + ' A successful database connection is required to proceed with installation.'; end; DatabaseConnectionPage := CreateInputQueryPage(wpSelectDir, 'Database Connection Configuration', 'Enter your Process Director database connection details.', DatabaseConnectionPageDescriptionText); DatabaseConnectionPage.Add('Server name (Data Source):', False); DatabaseConnectionPage.Add('Initial Catalog (Database):', False); DatabaseConnectionPage.Add('User ID (Uses SQL Server Authentication):', False); DatabaseConnectionPage.Add('Password:', True); DatabaseConnectionPage.Values[0] := ''; DatabaseConnectionPage.Values[1] := 'ProcessDirector'; DatabaseConnectionPage.Values[2] := ''; DatabaseConnectionPage.Values[3] := ''; // Custom page for Process Agent configuration ProcessAgentConfigurationPage := CreateInputQueryPage(DatabaseConnectionPage.ID, 'Process Agent Configuration', 'Enter the host name for the Process Agent running on this server.', 'Please specify the host name of the Process Agent for the instance of {#MyAppName} on this server. This name is used as the host name when assigning Process Director events to the server.'); ProcessAgentConfigurationPage.Add('Process Agent (Host Name):', False); ProcessAgentConfigurationPage.Values[0] := 'PAgent1'; end; // Process the replacement after installation procedure CurStepChanged(CurStep: TSetupStep); var ConfigFiles: array[0..0] of String; AppPath: String; ProcessAgentLogPath: String; DatabaseConnectionString: String; ErrorMsg: String; InsertStatement: String; begin if CurStep = ssPostInstall then begin // Get application path AppPath := ExpandConstant('{app}'); // Define config files to update ConfigFiles[0] := AppPath + '\appsettings.json'; // Build connection strings from user input DatabaseConnectionString := BuildDatabaseConnectionString(); if FileExists(ConfigFiles[0]) then begin ReplaceStringInFile(ConfigFiles[0], '"GreenfieldPD"', DatabaseConnectionString); ReplaceStringInFile(ConfigFiles[0], '"HostName"', ProcessAgentConfigurationPage.Values[0]); end; if DatabaseConnectionSucceeded = True then begin // Add record to ATServer database table with Process Agent name InsertStatement := 'INSERT INTO [dbo].[ATServer] ([HostName], [LogLevel], [LogFile], [Limit], [Interval], [Status], [RunExpired]) VALUES (N''' + ProcessAgentConfigurationPage.Values[0] + ''', 1, N''' + ProcessAgentLogPath + '\errorLog.txt'', 10, 10, N''Stopped'', 0);'; if MakeSQLConnection(DatabaseConnectionString, ErrorMsg, InsertStatement) = False then begin MsgBox('The insertion into ATServer failed. The Process Agent will not be registered and will need to be configured in iAdmin.', mbError, MB_OK); end; end; end; end; // Validate user input function NextButtonClick(CurPageID: Integer): Boolean; var Response: Integer; begin Result := True; if CurPageID = DatabaseConnectionPage.ID then begin // Test database connection before continuing DatabaseConnectionSucceeded := ConfirmDatabaseConnection(); Result := DatabaseConnectionSucceeded; // Setup launched with /BypassDatabaseConnection=True allows bypassing successful database connection if (Result = False) And (ExpandConstant('{param:BypassDatabaseConnection|false}') <> 'false') then begin Response := MsgBox('The database connection failed. Do you want to proceed with installation?' + #13#10#13#10 + 'Any database records normally created by this installer will not be created.' + #13#10 + 'Click "Yes" to proceed, or "No" to go back.', mbConfirmation, MB_YESNO); Result := (Response = IDYES); end; end; if CurPageID = ProcessAgentConfigurationPage.ID then begin // Validate that required fields are not empty if Trim(ProcessAgentConfigurationPage.Values[0]) = '' then begin MsgBox('Please enter a value for: Process Agent (Server Name).', mbError, MB_OK); Result := False; Exit; end; // Prompt user to check server name before proceeding Response := MsgBox('Have you verified the server name is unique?' + #13#10#13#10 + 'It is recommended to ensure the server name is unique before continuing.' + #13#10 + 'Click "Yes" to proceed, or "No" to go back.', mbConfirmation, MB_YESNO); Result := (Response = IDYES); end; end;