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
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;
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;
}
}
}
I need a setup page with two radio buttons. Clicking the second button should enable an input to define a path (Like it's done in TInputDirWizardPage).
Is it possible to customize TInputDirWizardPage for my needs?
Or do I need to build a CustomPage and define the controls by myself?
If the second question will be answered with yes, how am I able to use the "directory input" (from the TInputDirWizardPage), or is it also neccessary to build this on my own?
As you correctly guessed you have several options:
Start with TWizardPage and build everything from scratch
Start with TInputDirWizardPage and add the radio buttons
Start with TInputOptionWizardPage and add the edit box
The second option probably involves least customization. Though it needs a hack from Is it possible to allow a user to skip a TInputDirWizardPage in Inno Setup?
{ WORKAROUND }
{ Checkboxes and Radio buttons created on runtime do }
{ not scale their height automatically. }
{ See https://stackoverflow.com/q/30469660/850848 }
procedure ScaleFixedHeightControl(Control: TButtonControl);
begin
Control.Height := ScaleY(Control.Height);
end;
var
Page: TInputDirWizardPage;
DefaultLocationButton: TRadioButton;
CustomLocationButton: TRadioButton;
OldNextButtonOnClick: TNotifyEvent;
procedure LocationButtonClick(Sender: TObject);
begin
Page.Edits[0].Enabled := CustomLocationButton.Checked;
Page.Buttons[0].Enabled := CustomLocationButton.Checked;
end;
procedure NextButtonOnClick(Sender: TObject);
var
PrevDir: string;
begin
{ Do not validate, when "default location" is selected }
if (WizardForm.CurPageID = Page.ID) and DefaultLocationButton.Checked then
begin
PrevDir := Page.Values[0];
Page.Values[0] := GetWinDir; { Force value to pass validation }
OldNextButtonOnClick(Sender);
Page.Values[0] := PrevDir;
end
else
begin
OldNextButtonOnClick(Sender);
end;
end;
procedure InitializeWizard();
begin
Page := CreateInputDirPage(
wpWelcome,
'Select Personal Data Location', 'Where should personal data files be stored?',
'', False, 'New Folder');
Page.Add('');
DefaultLocationButton := TRadioButton.Create(WizardForm);
DefaultLocationButton.Parent := Page.Surface;
DefaultLocationButton.Top := Page.Edits[0].Top;
DefaultLocationButton.Caption := 'Use default location';
DefaultLocationButton.Checked := True;
DefaultLocationButton.OnClick := #LocationButtonClick;
ScaleFixedHeightControl(DefaultLocationButton);
CustomLocationButton := TRadioButton.Create(WizardForm);
CustomLocationButton.Parent := Page.Surface;
CustomLocationButton.Top :=
DefaultLocationButton.Top + DefaultLocationButton.Height + ScaleY(8);
CustomLocationButton.Caption := 'Use custom location';
CustomLocationButton.OnClick := #LocationButtonClick;
ScaleFixedHeightControl(DefaultLocationButton);
Page.Buttons[0].Top :=
Page.Buttons[0].Top +
((CustomLocationButton.Top + CustomLocationButton.Height + ScaleY(8)) -
Page.Edits[0].Top);
Page.Edits[0].Top :=
CustomLocationButton.Top + CustomLocationButton.Height + ScaleY(8);
Page.Edits[0].Left := Page.Edits[0].Left + ScaleX(16);
Page.Edits[0].Width := Page.Edits[0].Width - ScaleX(16);
Page.Edits[0].TabOrder := CustomLocationButton.TabOrder + 1;
Page.Buttons[0].TabOrder := Page.Edits[0].TabOrder + 1;
LocationButtonClick(nil); { Update edit for initial state of buttons }
OldNextButtonOnClick := WizardForm.NextButton.OnClick;
WizardForm.NextButton.OnClick := #NextButtonOnClick;
end;
More general question on the topic: Inno Setup Placing image/control on custom page.
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.