Backup external file before Inno Setup InstallDelete section - backup

I would make a backup of files and folders before the [InstallDelete] section deletes them
[Files]
Source: "{app}\res_mods\configs\wotstat\cache.json"; \
DestDir: "{app}\_backup\res_mods_{#DateTime}\configs\wotstat\"; \
Flags: external skipifsourcedoesntexist uninsneveruninstall
Source: "{app}\res_mods\0.9.17.1\vehicles\*"; \
DestDir:"{app}\_backup\res_mods_{#DateTime}\0.9.17.1\vehicles\"; \
Flags: external skipifsourcedoesntexist createallsubdirs recursesubdirs uninsneveruninstall
This works fine. But, if i check
[InstallDelete]
Type: filesandordirs; Name: "{app}\mods\*.*"; Tasks: cleanres
Type: filesandordirs; Name: "{app}\res_mods\*.*"; Tasks: cleanres
No files are saved
How I can made it work. Thx

The [InstallDelete] section is processed (as one would expect) before the [Files] section. See the installation order.
You can code the backup in the CurStepChanged(ssInstall) event, that happens before the installation starts:
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
SourcePath: string;
DestPath: string;
begin
if CurStep = ssInstall then
begin
SourcePath := ExpandConstant('{app}\res_mods\0.9.17.1\vehicles');
DestPath :=
ExpandConstant('{app}\_backup\res_mods_{#DateTime}\0.9.17.1\vehicles');
Log(Format('Backing up %s to %s before installation', [
SourcePath, DestPath]));
if not ForceDirectories(DestPath) then
begin
Log(Format('Failed to create %s', [DestPath]));
end
else
begin
DirectoryCopy(SourcePath, DestPath);
end;
SourcePath := ExpandConstant('{app}\res_mods\configs\wotstat\cache.json');
DestPath :=
ExpandConstant('{app}\_backup\res_mods_{#DateTime}\configs\wotstat');
if not ForceDirectories(DestPath) then
begin
Log(Format('Failed to create %s', [DestPath]));
end
else
begin
if not FileCopy(SourcePath, DestPath + '\cache.json', False) then
begin
Log(Format('Failed to copy %s', [SourcePath]));
end
else
begin
Log(Format('Backed up %s', [SourcePath]));
end;
end;
end;
end;
The code uses the DirectoryCopy function from the Inno Setup: copy folder, subfolders and files recursively in Code section.

Related

Opening a program using Remote Desktop on Windows server 2008 r2 from a program e written in Delphi 11.2

if PageControl1.ActivePage.Pageindex = 0 then
begin
rdp13 := TMsRdpClient7NotSafeForScripting.Create(Self);
with Rdp13 do
begin
Rdp13.align:= alclient;
Rdp13.BringToFront;
Rdp13.Server := s ;
Rdp13.UserName := 'xxx';
Rdp13.AdvancedSettings2.ClearTextPassword:= 'tttt';
Rdp13.RemoteProgram2.RemoteProgramMode:=True;
Rdp13.Connect;
RDP13.Onconnected := OnRDPConnect ;
procedure TForm2.Button7Click(Sender: TObject);
begin
rdp13.RemoteProgram.ServerStartProgram('C:\Program Files (x86)\xxxxxxx.exe', '', 'C:\Program Files (x86)\xxxxxx', false, '', false);
end ;
I can connect,but when use rdp13.RemoteProgram2.RemoteProgramMode:=True;
I always get a blank screen after connect.
Help Please

Inno Setup 6.2.1 web Download components problem

Having problems when Components section added to Inno Setup 6.2.1 download installation. Compiles ok, but unable to locate temporary file problem when code is run. What extra needs to be done when this download code is used for a components installer?
Simplified to a single custom install option.
Environment: Inno Setup 6.2.1, Windows 7
[Setup]
PrivilegesRequired=lowest
[Types]
Name: "custom"; Description: "Custom installation"; Flags: iscustom
[Components]
Name: "test"; Description: "Test File"; Types: custom; Flags: exclusive
[Files]
Source: "{tmp}\version.txt"; DestDir: "{userappdata}\wire"; DestName: "test.txt"; Components: "test"; Flags: external ignoreversion
[Code]
var
DownloadPage: TDownloadWizardPage;
function OnDownloadProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean;
begin
if Progress = ProgressMax then
Log(Format('Successfully downloaded file to {tmp}: %s', [FileName]));
Result := True;
end;
procedure InitializeWizard;
begin
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), #OnDownloadProgress);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpReady then begin
DownloadPage.Clear
DownloadPage.Add('http://wireshare.sourceforge.net/WSSecurityUpdates/version', 'version.txt', '');
DownloadPage.Show;
try
try
DownloadPage.Download; // This downloads the files to {tmp}
Result := True;
except
if DownloadPage.AbortedByUser then
Log('Aborted by user.')
else
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end else
Result := True;
end;

Inno Setup: Unable to unload and delete a DLL required by install from the {tmp} folder at end of install [duplicate]

I'm extending my Inno-Setup script with code that I can best implement in C# in a managed DLL. I already know how to export methods from a managed DLL as functions for use in an unmanaged process. It can be done by IL weaving, and there are tools to automate this:
NetDllExport (written by me)
UnmanagedExports
So after exporting, I can call my functions from Pascal script in an Inno-Setup installer. But then there's one issue: The DLL can't seem to be unloaded anymore. Using Inno-Setup's UnloadDLL(...) has no effect and the file remains locked until the installer exits. Because of this, the setup waits for 2 seconds and then fails to delete my DLL file from the temp directory (or install directory). In fact, it really stays there until somebody cleans up the drive.
I know that managed assemblies cannot be unloaded from an AppDomain anymore, unless the entire AppDomain is shut down (the process exits). But what does it mean to the unmanaged host process?
Is there a better way to allow Inno-Setup to unload or delete my DLL file after loading and using it?
As suggested in other answers, you can launch a separate process at the end of the installation that will take care of the cleanup, after the installation processes finishes.
A simple solution is creating an ad-hoc batch file that loops until the DLL file can be deleted and then also deletes the (now empty) temporary folder and itself.
procedure DeinitializeSetup();
var
FilePath: string;
BatchPath: string;
S: TArrayOfString;
ResultCode: Integer;
begin
FilePath := ExpandConstant('{tmp}\MyAssembly.dll');
if not FileExists(FilePath) then
begin
Log(Format('File %s does not exist', [FilePath]));
end
else
begin
BatchPath :=
ExpandConstant('{%TEMP}\') +
'delete_' + ExtractFileName(ExpandConstant('{tmp}')) + '.bat';
SetArrayLength(S, 7);
S[0] := ':loop';
S[1] := 'del "' + FilePath + '"';
S[2] := 'if not exist "' + FilePath + '" goto end';
S[3] := 'goto loop';
S[4] := ':end';
S[5] := 'rd "' + ExpandConstant('{tmp}') + '"';
S[6] := 'del "' + BatchPath + '"';
if not SaveStringsToFile(BatchPath, S, False) then
begin
Log(Format('Error creating batch file %s to delete %s', [BatchPath, FilePath]));
end
else
if not Exec(BatchPath, '', '', SW_HIDE, ewNoWait, ResultCode) then
begin
Log(Format('Error executing batch file %s to delete %s', [BatchPath, FilePath]));
end
else
begin
Log(Format('Executed batch file %s to delete %s', [BatchPath, FilePath]));
end;
end;
end;
You could add a batch script (in the form of running cmd -c) to be executed at the end of setup that waits for the file to be deletable and deletes it. (just make sure to set the inno option to not wait for the cmd process to complete)
You could also make your installed program detect and delete it on first execution.
As suggested in this Code Project Article : https://www.codeproject.com/kb/threads/howtodeletecurrentprocess.aspx
call a cmd with arguments as shown below.
Process.Start("cmd.exe", "/C ping 1.1.1.1 -n 1 -w 3000 > Nul & Del " + Application.ExecutablePath);
But basically as #Sean suggested, make sure you dont wait for the cmd.exe to exit in your script.
While not exactly an answer to your question, can't you just mark the DLL to be deleted next time the computer is restarted?
Here's what I did, adapted from Martin's great answer. Notice the 'Sleep', this did the trick for me. Because the execution is called in a background thread, that is not a blocker, and leaves sufficient time for InnoSetup to free up the resources.
After doing that, I was able to clean the temporary folder.
// Gets invoked at the end of the installation
procedure DeinitializeSetup();
var
BatchPath: String;
S: TArrayOfString;
FilesPath: TStringList;
ResultCode, I, ErrorCode: Integer;
begin
I := 0
FilesPath := TStringList.Create;
FilesPath.Add(ExpandConstant('{tmp}\DLL1.dll'));
FilesPath.Add(ExpandConstant('{tmp}\DLL2.dll'));
FilesPath.Add(ExpandConstant('{tmp}\DLLX.dll'));
while I < FilesPath.Count do
begin
if not FileExists(FilesPath[I]) then
begin
Log(Format('File %s does not exist', [FilesPath[I]]));
end
else
begin
UnloadDLL(FilesPath[I]);
if Exec('powershell.exe',
FmtMessage('-NoExit -ExecutionPolicy Bypass -Command "Start-Sleep -Second 5; Remove-Item -Recurse -Force -Path %1"', [FilesPath[I]]),
'', SW_HIDE, ewNoWait, ErrorCode) then
begin
Log(Format('Temporary file %s successfully deleted', [ExpandConstant(FilesPath[I])]));
end
else
begin
Log(Format('Error while deleting temporary file: %s', [ErrorCode]));
end;
inc(I);
end;
end;
Exec('powershell.exe',
FmtMessage('-NoExit -ExecutionPolicy Bypass -Command "Start-Sleep -Second 5; Remove-Item -Recurse -Force -Path %1"', [ExpandConstant('{tmp}')]),
'', SW_HIDE, ewNoWait, ErrorCode);
Log(Format('Temporary folder %s successfully deleted', [ExpandConstant('{tmp}')]));
end;
The easy way to do what you want is through an AppDomain. You can unload an AppDomain, just not the initial one. So the solution is to create a new AppDomain, load your managed DLL in that and then unload the AppDomain.
AppDomain ad = AppDomain.CreateDomain("Isolate DLL");
Assembly a = ad.Load(new AssemblyName("MyManagedDll"));
object d = a.CreateInstance("MyManagedDll.MyManagedClass");
Type t = d.GetType();
double result = (double)t.InvokeMember("Calculate", BindingFlags.InvokeMethod, null, d, new object[] { 1.0, 2.0 });
AppDomain.Unload(ad);
Here is what the DLL code looks like...
namespace MyManagedDll
{
public class MyManagedClass
{
public double Calculate(double a, double b)
{
return a + b;
}
}
}

sqlcmd not working inside inno setup even with result code equals zero

After googling a bunch and struggling for a day with no result, here I am.
I'm developing an installer using Inno Setup, which installs and properly configures MS SQL Server 2008 R2 according some user input from a wizard page containing checkboxes.
I have a procedure to do that SQL Server configuration setted like this:
Source: ".\src\{#ExecName}"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: runSQLCMD
where #ExecName is my .exe name;
here is the procedure:
[Code]
procedure runSQLCMD();
var
ResultCode: Integer;
SQLCMDPath: String;
SQLParams: String;
BasicQuery: String;
FullQuery: String;
begin
SQLCMDPath := ExpandConstant('{pf}') + '\Microsoft SQL Server\100\Tools\Binn\sqlcmd.exe'
BasicQuery := 'EXEC sp_configure ''show advanced option'', 1; RECONFIGURE; EXEC sp_configure ''max worker threads'', 256; RECONFIGURE; EXEC sp_configure ''backup compression default'', 1; RECONFIGURE; EXEC sp_configure ''max degree of parallelism'', 4; RECONFIGURE; EXEC sp_configure ''max server memory'', 512; RECONFIGURE;';
FullQuery := 'EXEC sp_configure ''show advanced option'', 1; RECONFIGURE; EXEC sp_configure ''backup compression default'', 1; RECONFIGURE; EXEC sp_configure ''max degree of parallelism'', 4; RECONFIGURE; EXEC sp_configure ''max server memory'', 1024; RECONFIGURE;';
if not FileExists(SQLCMDPath) then begin
SQLCMDPath := ExpandConstant('{pf64}') + '\Microsoft SQL Server\100\Tools\Binn\sqlcmd.exe'
end;
if (ShouldRunItem(1)) then begin
SQLParams := '-S .\INSTANCE -U sa -P "password" -Q "' + BasicQuery + '-o ' + ExpandConstant('{tmp}') + '\logsql.txt';
if (Exec(SQLCMDPath, SQLParams, '',SW_SHOW,ewWaitUntilIdle, ResultCode)) then begin
MsgBox('Success!', mbInformation, MB_OK);
end
else begin
MsgBox('Error', mbError, MB_OK);
end;
end;
if (ShouldRunItem(2)) then begin
SQLParams := '-S .\INSTANCE -U sa -P "password" -Q "' + FullQuery + '-o ' + ExpandConstant('{tmp}') + '\logsql.txt';
if (Exec(SQLCMDPath, SQLParams, '',SW_SHOW,ewWaitUntilIdle, ResultCode)) then begin
MsgBox('Success!', mbInformation, MB_OK);
end
else begin
MsgBox('Error', mbError, MB_OK);
end;
end;
end;
When the Exec statement runs, the ResultCode variable returns zero on debug, but when I right-click on server, point to Properties/Memory (on SSMS), the Memory configuration is not set with the user-chosen value. Not even the logsql.txt file is generated (I've opened the directory pointed by {tmp} constant and no .txt files there)
Running sqlcmd from cmd, using the same parameters and query, everything goes fine.
What I'm doing wrong?
Thank you in advance.
In my case it was not running because I had added "" around the SQLCMDPath variable. Once I removed them the -Q was executed successfully. Also, I used {commonpf} instead of {pf} as such := ExpandConstant('{commonpf}\Microsoft SQL Server\100\Tools\Binn\sqlcmd.exe');
Full code
procedure CurStepChanged(CurStep: TSetupStep);
var
sqlCmd: String;
dbCreate: String;
path: String;
ResultCode: Integer;
begin
path := ExpandConstant('{app}');
sqlCmd := ExpandConstant('{commonpf}\Microsoft SQL Server\100\Tools\Binn\sqlcmd.exe');
dbCreate := ' -S .\IVA -U sa -P sql -Q "CREATE DATABASE pos ON PRIMARY (NAME = N''pos'', FILENAME = N''' + path + '\pos.mdf'', SIZE = 73728KB, MAXSIZE = UNLIMITED ,FILEGROWTH = 65536KB) LOG ON (NAME = N''pos_log'', FILENAME = N''' + path + '\pos_log.ldf'', SIZE = 73728KB, MAXSIZE = 2048GB,FILEGROWTH = 65536KB)" -o "' + ExpandConstant('{app}\install.log') + '"';
// CurStep values
// ssInstall, ssPostInstall, ssDone
if CurStep = ssPostInstall then begin
// use log to view and test the string in a command window
Log('The Value is dbCreate: ' + dbCreate );
//MsgBox(sqlCmd + dbCreate, mbConfirmation, MB_YESNO)
if Exec(sqlCmd, dbCreate, '' , SW_HIDE, ewWaitUntilIdle, ResultCode) then
begin
MsgBox( 'Database is ready for use' , mbInformation, mb_Ok);
end
else begin
MsgBox(SysErrorMessage(ResultCode) , mbInformation, mb_Ok);
end;
end;
end;

How to use Inno Setup to update a database using .sql script

I'd like to compile a setup that will connect to a remote database using the credentials provided by the user, then install few db components using .sql script.
Is that possible using Inno Setup?
More details:
I'd like to have a custom form, asking the user to enter the database address and credentials, then run a command that will execute an sql script that will update the remote database server.
If the update is successful - complete the installation with success.
This is rather general question - I have a lot of customized setups that should connect to different servers/run different scripts - the idea is to build a generic form that will provide this functionality.
I don't think you can have a completely generic form, as for different servers you may need either a single connection string, or a server name and an (optional) port; for some servers you will use system authentication, for others a user name password tuple.
Having said that I will give you a small demo Inno script that asks for server name and port, user name and password, then makes a few tests, then executes an application that is extracted (by code) to the temp directory and will be deleted by the installer. You can use this as a starting point for your scripts. Having a few of such snippets, and including them in your scripts as necessary will probably be all you need:
[Setup]
AppID=DBUpdateTest
AppName=Test
AppVerName=Test 0.1
AppPublisher=My Company, Inc.
DefaultDirName={pf}\Test
DefaultGroupName=Test
DisableDirPage=yes
DisableProgramGroupPage=yes
OutputBaseFilename=setup
PrivilegesRequired=none
[Files]
Source: "isql.exe"; DestDir: "{tmp}"; Flags: dontcopy
Source: "update_V42.sql"; DestDir: "{tmp}"; Flags: dontcopy
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Code]
var
DBPage: TInputQueryWizardPage;
procedure InitializeWizard;
begin
DBPage := CreateInputQueryPage(wpReady,
'Database Connection Information', 'Which database is to be updated?',
'Please specify the server and the connection credentials, then click Next.');
DBPage.Add('Server:', False);
DBPage.Add('Port:', False);
DBPage.Add('User name:', False);
DBPage.Add('Password:', True);
DBPage.Values[0] := GetPreviousData('Server', '');
DBPage.Values[1] := GetPreviousData('Port', '');
DBPage.Values[2] := GetPreviousData('UserName', '');
DBPage.Values[3] := GetPreviousData('Password', '');
end;
procedure RegisterPreviousData(PreviousDataKey: Integer);
begin
SetPreviousData(PreviousDataKey, 'Server', DBPage.Values[0]);
SetPreviousData(PreviousDataKey, 'Port', DBPage.Values[1]);
SetPreviousData(PreviousDataKey, 'UserName', DBPage.Values[2]);
SetPreviousData(PreviousDataKey, 'Password', DBPage.Values[3]);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if CurPageID = DBPage.ID then begin
if DBPage.Values[0] = '' then begin
MsgBox('You must enter the server name or address.', mbError, MB_OK);
Result := False;
end else if DBPage.Values[2] = '' then begin
MsgBox('You must enter the user name.', mbError, MB_OK);
Result := False;
end else if DBPage.Values[3] = '' then begin
MsgBox('You must enter the user password.', mbError, MB_OK);
Result := False;
end else begin
ExtractTemporaryFile('isql.exe');
ExtractTemporaryFile('update_V42.sql');
if Exec(ExpandConstant('{tmp}') + '\isql.exe', '--user ' + DBPage.Values[2]
+ ' --password ' + DBPage.Values[3] + ' --database ' + DBPage.Values[0]
+ ':foo --script update_V42.sql', '',
SW_HIDE, ewWaitUntilTerminated, ResultCode)
then begin
// check ResultCode and set Result accordingly
Result := ResultCode = 0;
end else begin
MsgBox('Database update failed:'#10#10 + SysErrorMessage(ResultCode),
mbError, MB_OK);
Result := False;
end;
end;
end;
end;
Beware: I haven't fully tested this, so there may be more code necessary to properly clean everything up. Error handling is definitely missing!