Deleting files from the users profile when uninstalling an Inno based setup - c++

Is it possible to delete files from this folder and how?
Currently i have this which doesnt work
[UninstallDelete]
Type: files; Name: "{userappdata}\Roaming\Myapp\*";
Type: files; Name: "{commonappdata}\Roaming\Myapp\*";
Type: files; Name: "{app}\*.*";
[Code]
/////////////////////////////////////////////////////////////////////
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
/////////////////////////////////////////////////////////////////////
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
/////////////////////////////////////////////////////////////////////
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString
// default return value
Result := 0;
// get the uninstall string of the old app
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
/////////////////////////////////////////////////////////////////////
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;

While Inno can remove files from the profile of the user running the uninstaller, it can not touch any other user's profile.
If the user trying to run the uninstaller is a limited user, it will ask for the admin details and so any {user...} constants will resolve to the admin user, NOT the limited user.
Your best option in this case is it leave any of the user's configuration and data in case they want to reinstall or have roaming profiles. Also note that in your code above, the old setup is removed during the upgrade which will trigger the "remove all the user's data" option. I'm sure that's not what you want...

Related

Record being modified but can't find where even exporting all objects to txt

In Dynamics Nav 2018, I'm using the selected action in this page rows, when I click "Validar", all selected rows will change it's status to "Pendiente".
After some time, the row will change to status "Error":
I can see in debugger when it changes from "Registrando" to "Pendiente". But I can't see when it's changing to "Error" and when it's inserting data to column "Descripción Estado":
Validar - OnAction()
UserSetup.GET(USERID);
UserSetup.TESTFIELD("Superusuario SII",TRUE);
//++SII V.08
CurrPage.SETSELECTIONFILTER(SII);
WITH SII DO BEGIN
IF FINDSET(TRUE,TRUE) THEN
REPEAT
//--SII V.08
//++SII JUL-2018
IF SII."Pendiente de validar" THEN BEGIN
TempHistSII.RESET;
TempHistSII.SETCURRENTKEY("N Doc NAV","Fecha Registro");
TempHistSII.SETRANGE("N Doc NAV",SII."N Doc NAV");
TempHistSII.SETRANGE("Fecha Registro",SII."Fecha Registro");
TempHistSII.SETRANGE(TempHistSII."Nº Mov",SII."Nº Mov");
//++SII JUL-2018 12/06/18
//IF TempHistSII.FINDFIRST THEN BEGIN
IF TempHistSII.FINDLAST THEN BEGIN
//--SII JUL-2018 12/06/18
HistRegSII.INIT;
HistRegSII.TRANSFERFIELDS(TempHistSII);
HistRegSII."Usuario modificación" := USERID;
HistRegSII."Fecha modificación" := TODAY;
HistRegSII."Hora modificación" := TIME;
HistRegSII.INSERT;
//++SII JUL-2018 12/06/18
//Inserto en tabla temporal
TempHistSII.INIT;
TempHistSII.TRANSFERFIELDS(SII);
TempHistSII."Nº línea registro" := HistRegSII."Nº línea registro" + 10000;
TempHistSII.INSERT;
//--SII JUL-2018 12/06/18
END;
SII."Pendiente de validar" := FALSE;
END;
//--SII JUL-2018
CASE Estado OF
//++SII JUL-2018
//Estado::"Registrada con error",Estado::Registrada: //++SII V.07
Estado::"Registrada con error",Estado::Registrada,Estado::"Error por inmuebles adicionales":
//--SII JUL-2018
BEGIN
"Tipo comunicacion" := "Tipo comunicacion"::A1;
Estado := Estado::Pendiente;
"Descripción Estado" := '';
END;
//++SII V.06
//Estado::Error:
Estado::Error,Estado::"Error configuración":
//--SII V.06
BEGIN
Estado := Estado::Pendiente;
"Descripción Estado" := '';
END;
//++SII V.07
Estado::Pendiente,Estado::"Pendiente Devengo":
"Descripción Estado" := '';
//--SII V.07
ELSE
ERROR(Text001,Estado);
END;
MODIFY;
//++SII V.08
UNTIL NEXT = 0;
END;
//--SII V.08
So I can see there when it's changing the status when clicking "Validar" button... but I can't find the process that is changing later the status to "Error"...
I'm checking the Job Queue Entries and I don't see anything that affects this columns.
Using the tool whereused I can't find anything.
I've also checked the table triggers and there's nothing, all empty.
I don't know how it's possible that I can't find anything exporting all objects to txt... I should be able to search "Descripción Estado" := or No existe el Registro
What could write to this column that it's not displayed in txt objects and not triggering the debugger?
Table can still be modified through SQL. This you will only find using SQL Server Profiler.
THEORETICALLY your version supports extensions v1, which you will not see when exporting objects to txt. So check if you have any extensions installed.
Maybe you just searching wrong. Try using Statical Prism. This is a greater tool for code exploring.
There is a trick I like to use. Your version supports subscriptions. Create a codeunit, put a subscriber function to it, subscribe to table's OnModify event and just throw an error when record is being modified and the value of the field is error. This will break some processes so do this on test DB. Then reproduce the behavior. Then check windows logs on server where nav instance is installed. Find your error in the log. Along with the error you will find stack trace. By stack trace you will see which object caused record modification.

Get path param value into ORDS prehook

Is there a way to get pathparam from url with get_cgi_env? Example: https://clientes/{codigo}. I would like to get the value from :codigo pathparam.
Into handler GET, POST, PUT, etc. on endpoint it is possible to get the pathparam value but it's not clear how can to be done into ORDS prehook.
I have this function to get the complete url - the "QUERY_STRING" portion should give you the parameters
FUNCTION request_url RETURN VARCHAR2
IS
l_url VARCHAR2(1024);
BEGIN
l_url := owa_util.get_cgi_env('SERVER_NAME') ||':' ||
owa_util.get_cgi_env('SERVER_PORT') ||
owa_util.get_cgi_env('SCRIPT_NAME') ||
owa_util.get_cgi_env('PATH_INFO') ||
owa_util.get_cgi_env('QUERY_STRING');
RETURN l_url;
EXCEPTION WHEN VALUE_ERROR THEN
RETURN 'unable to retrieve request_url';
END request_url;
You can access all the CGI variables available via owa.cgi_var_name and owa.cgi_var_val (owa.num_cgi_vars for the count).
I have this running in my preHook:
pragma autonomous_transaction;
l_obj json_object_t := json_object_t();
l_json blob;
begin
for i in 1 .. owa.num_cgi_vars loop
l_obj.put(owa.cgi_var_name(i), owa.cgi_var_val(i));
end loop;
l_json := l_obj.to_blob;
insert into rest_access
(id, created, cgi_variables, current_schema)
values
(s_rest_access.nextval, sysdate, l_json, sys_context('userenv', 'current_schema'));
commit;
Lots of values that you have access to, including parameters.
To see a list of parameters after running it and logging requests, simply run
select *
from json_table((select json_dataguide(cgi_variables, dbms_json.format_flat) from rest_access),
'$[*]' columns( --
opath varchar2(200) path '$."o:path"',
otype varchar2(40) path '$.type',
olength number path '$."o:length"')) j;

Oracle APEX Set value to Page Item from Ajax process

I'm trying to upload document from APEX page to S3 bucket and I'm successful with the help of plugin from https://www.apexutil.com/ords/prod/f?p=700:200:1349309139567:::::
The plugin demonstrate retrieve the upload result and inserting to table as explained below.
Create new dynamic action;
Choose Upload Success [FM Component];
Choose "Execute JavaScript Code" action;
Inside the "Code" textarea define code:
apex.server.process("my_ajax_process", {
x01: this.browserEvent.originalEvent.detail.serverId,
x02: this.browserEvent.originalEvent.detail.file.name,
x03: this.browserEvent.originalEvent.detail.file.body.size,
x04: this.browserEvent.originalEvent.detail.file.body.type
}, {
success: function() {
console.log("success");
},
and retrieve the x0 values in Ajax process and inserting to table.
Create Ajax Process, name: "my_ajax_process"
Define PL/SQL code:
declare
l_server_id varchar2(4000) := apex_application.g_x01;
l_name varchar2(4000) := apex_application.g_x02;
l_size number := apex_application.g_x03;
l_type varchar2(4000) := apex_application.g_x04;
begin
insert into MY_TABLE (MT_SERVER_ID, MT_NAME, MT_SIZE, MT_TYPE)
values (l_server_id, l_name, l_size, l_type);
owa_util.status_line(nstatus => 204, creason => 'No Content');
end;
However I need to capture the X0 values in the Ajax process and assign to Form pages items, as I have to store the output along with other form fields and store into my table. I have tried and however its not getting updated. Any help much appreciated
declare
l_server_id varchar2(4000) := apex_application.g_x01;
l_name varchar2(4000) := apex_application.g_x02;
l_size number := apex_application.g_x03;
l_type varchar2(4000) := apex_application.g_x04;
begin
:P12_DOCUMENTURL := l_server_id;
:P12_DOCUMENTTYPE := l_type;
:P12_DOCSIZE := l_size;
owa_util.status_line(nstatus => 204, creason => 'No Content');
end;
:P12_DOCUMENTURL := l_server_id; -- This kind of assignment wont work in an ajax process
But you can use APEX_UTIL.SET_SESSION_STATE
BEGIN
APEX_UTIL.SET_SESSION_STATE('my_item','myvalue');
END;
Example
BEGIN
APEX_UTIL.SET_SESSION_STATE('P12_DOCUMENTURL',l_server_id);
END;
More details here -
https://docs.oracle.com/cd/E37097_01/doc.42/e35127/GUID-62AA4333-160D-44FD-9F07-D188A2F4BC55.htm#AEAPI181
Also please take a look at this https://jeffkemponoracle.com/2014/02/apex_util-set_session_state-may-or-may-not-commit/
For some more discussion about the commits issued.
Finally this works for me.
A dynamic action
var l01 = this.browserEvent.originalEvent.detail.serverId;
var l03 = this.browserEvent.originalEvent.detail.file.body.size;
var l04 = this.browserEvent.originalEvent.detail.file.body.type;
apex.server.process
( "upload_ajax_process",
{},
{ success: function( pData ) {
$s("P12_DOCUMENTURL",l01 );
$s("P12_DOCSIZE", l03);
$s("P12_DOCUMENTTYPE", l04);
}
}
);
Also Ajax process "upload_ajax_process" with some dummy statement. Without this I'm getting "Error: SyntaxError: Unexpected token P in JSON at position 0"
begin
owa_util.status_line(nstatus => 204, creason => 'No Content');
end;

Inno Setup: Inherited OLE-Object properties not accessible?

I followed this question's accepted answer to query the machines network adapters. It finally worked but I still face a problem reading the values of these properties:
Win32_NetworkAdapterConfiguration.Caption
Win32_NetworkAdapterConfiguration.Description
Every time the code reaches this line where networkAdapter.Caption is called a runtime error is produced saying:
Runtime error (at 60:8952): Unknown method.
This is my code, adopted from the above mentioned Stack Overflow question:
Log('Querying WMI for network adapter data...');
query := 'SELECT IPEnabled, IPAddress, MACAddress, InterfaceIndex FROM Win32_NetworkAdapterConfiguration';
networkAdapters := wbemServices.ExecQuery(query);
if not VarIsNull(networkAdapters) then
begin
for i := 0 to networkAdapters.Count - 1 do
begin
networkAdapter := networkAdapters.ItemIndex(i);
if (not VarIsNull(networkAdapter.MACAddress)) and networkAdapter.IPEnabled and (not VarIsNull(networkAdapter.IPAddress)) then
begin
SetArrayLength(sysInfo.networkAdapters, GetArrayLength(sysInfo.networkAdapters) + 1);
nicRec := sysInfo.networkAdapters[adapterIndex];
{ Adapter name }
nicRec.name := defaultIfNull(networkAdapter.Caption, Format('Adapter %d', [i]));
Log(Format(' NIC[%d] name........: %s', [adapterIndex, nicRec.name]));
{ Adapter index }
nicRec.index := defaultIfNull(networkAdapter.InterfaceIndex, adapterIndex);
Log(Format(' NIC[%d] index.......: %d', [adapterIndex, nicRec.index]));
{ Adapter MAC address }
nicRec.macAddress := defaultIfNull(networkAdapter.MACAddress, '');
Log(Format(' NIC[%d] MAC address.: %s', [adapterIndex, nicRec.macAddress]));
{ Adapter ip address(es) }
nicRec.ipAddresses := TStringList.Create;
if not VarIsNull(networkAdapter.IPAddress) then
begin
ips := networkAdapter.IPAddress;
for j := 0 to GetArrayLength(ips) - 1 do
begin
nicRec.ipAddresses.Add(ips[j]);
Log(Format(' NIC[%d] IPv4 address: %s', [adapterIndex, nicRec.ipAddresses.Strings[j]]));
end;
end;
adapterIndex := adapterIndex + 1;
end;
end;
end;
After some reading in the Microsoft docs, I came across the description of these properties. It states, that the Win32_NetworkAdapterConfiguration class extends the CIM_Setting class. The properties Caption and Description are defined there. Is this an issue with the Inno Setup compiler (I am using the latest 6.0.2) or do I have to apply some sort of cast to may variant variable?
Of course that inherited properties are accessible. Actually Inno Setup does not even care what class is that. It uses late binding. Property name resolution is delegated to the class itself.
But you are not working with Win32_NetworkAdapterConfiguration. IWbemServices.ExecQuery returns IEnumWbemClassObject, which in turn enumerates IWbemClassObject. And that contains results of your query. Your query does not ask for Caption and Description properties, so the result set naturally does not contain them.
Add the properties to the query:
Query := 'SELECT IPEnabled, IPAddress, MACAddress, InterfaceIndex, Caption, Description FROM Win32_NetworkAdapterConfiguration';

Avoiding Dialog Boilerplate in Delphi and /or C++

I often need to design a dialog in Delphi/C++Builder that allows various properties of an object to be modified, and the code to use it typically looks like this.
Dialog.Edit1.Text := MyObject.Username;
Dialog.Edit2.Text := MyObject.Password;
// ... many more of the same
if (Dialog.ShowModal = mrOk)
begin
MyObject.Username := Dialog.Edit1.Text;
MyObject.Password := Dialog.Edit2.Text;
// ... again, many more of the same
end;
I also often need similar code for marshalling objects to/from xml/ini-files/whatever.
Are there any common idioms or techniques for avoiding this kind of simple but repetitive code?
well, something that I feel completely invaluable is the GExperts plugin wizard "Reverse Statement" which is invoked after installing GExperts by pressing Shift + ALT + R
What it does is automatically switch the assignments around for the highlighted block. For example:
edit1.text := dbfield.asString;
becomes
dbField.asString := edit1.text;
Not exactly what your looking for, but a huge time saver when you have a large number of assignments.
Here's my variation on this. What I did, having got fed up with the same repetitive code, was to name all the edit boxes according to the XML node names I wanted, then iterate around the components and output their values. The XML code should be obvious, and I only have an edit and checkbox, but you should be able to see the idea.
procedure TfrmFTPSetup.LoadFromXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;
xDocument := TNativeXml.Create;
try
xDocument.LoadFromFile(szFileName);
xMainNode := xml_ChildNodeByName(xDocument.Root, 'options');
for nLoop := 0 to ComponentCount - 1 do
begin
xComponent := Components[nLoop];
if xComponent is TRzCustomEdit then
begin
(xComponent as TRzCustomEdit).Text := xMainNode.AttributeByName[xComponent.Name];
end;
if xComponent is TRzCheckBox then
begin
(xComponent as TRzCheckBox).Checked := xml_X2Boolean(xMainNode.AttributeByName[xComponent.Name], false);
end;
end;
finally
FreeAndNil(xDocument);
end;
end;
procedure TfrmFTPSetup.SaveToXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;
xDocument := TNativeXml.CreateName('ftpcontrol');
try
xMainNode := xml_ChildNodeByNameCreate(xDocument.Root, 'options');
for nLoop := 0 to ComponentCount - 1 do
begin
xComponent := Components[nLoop];
if xComponent is TRzCustomEdit then
begin
xMainNode.AttributeByName[xComponent.Name] := (xComponent as TRzCustomEdit).Text;
end;
if xComponent is TRzCheckBox then
begin
xMainNode.AttributeByName[xComponent.Name] := xml_Boolean2X((xComponent as TRzCheckBox).Checked);
end;
end;
xDocument.XmlFormat := xfReadable;
xDocument.SaveToFile(szFileName);
finally
FreeAndNil(xDocument);
end;
end;
It's not considered good practice to access properties of visual components on a form. It is considered better to have seperate properties. In the example above you would have username and password properties with get and set methods.
For example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
private
function GetPassword: string;
function GetUsername: string;
procedure SetPassword(const Value: string);
procedure SetUsername(const Value: string);
public
property Password: string read GetPassword write SetPassword;
property Username: string read GetUsername write SetUsername;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.GetPassword: string;
begin
Result := Edit2.Text;
end;
function TForm1.GetUsername: string;
begin
Result := Edit1.Text;
end;
procedure TForm1.SetPassword(const Value: string);
begin
Edit2.Text := Value;
end;
procedure TForm1.SetUsername(const Value: string);
begin
Edit1.Text := Value;
end;
end.
This means you can change the visual components on the form without having it affecting the calling code.
Another option would be to pass the object as a property to the dialog;
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TUserObject = class(TObject)
private
FPassword: string;
FUsername: string;
public
property Password: string read FPassword write FPassword;
property Username: string read FUsername write FUsername;
end;
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
btnOK: TButton;
procedure btnOKClick(Sender: TObject);
private
FUserObject: TUserObject;
procedure SetUserObject(const Value: Integer);
public
property UserObject: Integer read FUserObject write SetUserObject;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnOKClick(Sender: TObject);
begin
FUserObject.Username := Edit1.Text;
FUserObject.Password := Edit2.Text;
ModalResult := mrOK;
end;
procedure TForm1.SetUserObject(const Value: Integer);
begin
FUserObject := Value;
Edit1.Text := FUserObject.Username;
Edit2.Text := FUserObject.Password;
end;
end.
Hope that helps.
Delphi at least have 'With', though it doesn't solve the problem completely.
if (Dialog.ShowModal = mrOk)
begin
with MyObject do
begin
Username := Dialog.Edit1.Text;
Password := Dialog.Edit2.Text;
// ... again, many more of the same
end;
end;
And builder AFAIK has nothing alike.
Binding controls to data works well in Delphi, but unfortunately only when that data resides in a TDataSet descendant. You could write a TDataSet descendant that uses an object for data storage, and it turns out that one such thing already exists. See link below... This implementation appears to only work with collections of objects (TCollection or TObjectList), not single objects.
http://www.torry.net/pages.php?id=563 - search the page for for "Snap Object DataSet"
I have no personal experience with this, but it would be very useful if it works and especially if it would also work with single object instances, such as a property on a data module...
Look up "mediator pattern". It's a GoF design pattern, and in their book the GoF in fact motivate this design pattern with a somewhat similar situation to what you're describing here. It aims at solving a different problem -- coupling -- but I think you have this problem too anyhow.
In short, the idea is to create a dialog mediator, an extra object that sits in between all the dialog widgets. No widget knows about any other widget, but each widget does know the mediator. The mediator knows all widgets. When one widget changes it informs the mediator; the mediator then informs the relevant widgets about this. For example, when you click OK the mediator may inform other widgets about this event.
This way each widgets takes care of events and actions related to itself only. The mediator takes care of the interaction between all widgets, so all this "boilerplate" code is split over all widgets, and the "residue" which is global to all widgets is the interaction, and it is the responsibility of the mediator.