Delphi 10 comes with Windows RT headers translated to Pascal. Based on this C++ code i am trying to enumerate all installed Metro applications in Windows 8+. The only problem is, that i don't know how to get iterator correctly, from IIterable_1__IPackage. FindPackages method of Deployment_IPackageManager is called correctly, and i can confirm it by scanning memory process (before and after method call). Process memory contains strings like Microsoft.SkypeApp etc. so packages are ready to be iterated. Once i set IIterator_1__IPackage to first package, it becomes invalid pointer value (on my PC that is $3). I wonder now, if it has something to do with incorrect RT headers in Delphi or me approaching iteration process wrong way (most likely). Current code:
program PackagesManager;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
System.SysUtils,
WinAPi.Management,
WinApi.ApplicationModel,
WinApi.WinRT,
System.Win.ComObj;
var
LClassId: HString;
pInspectable: IInspectable;
pAct: IActivationFactory;
packageManager: Deployment_IPackageManager;
pkgs: IIterable_1__IPackage;
pIter: IIterator_1__IPackage;
hasCurrent: Boolean;
packageId: IPackageId;
package: IPackage;
begin
// COM/Runtime Object Initialization
OleCheck(RoInitialize(RO_INIT_SINGLETHREADED));
try
if Succeeded(WindowsCreateString(PWideChar(SDeployment_PackageManager),
Length(SDeployment_PackageManager), LClassId)) then
begin
// Get the activation factory
OleCheck(RoGetActivationFactory(LClassId, IActivationFactory,
pInspectable));
// Activate or create an instance from the Activation Factory
pAct := pInspectable as IActivationFactory;
pAct.ActivateInstance(pInspectable);
// Extract the PackageManager via QueryInterface
OleCheck(pInspectable.QueryInterface(Deployment_IPackageManager,
packageManager));
// Get the `IIterable` Collection of all packages
//
// after below call, process memory contains strings with package names
// i.e. Microsoft.SkypeApp, it means FindPackages was called properly
// confirmed this by scanning process memory for known strings,
// before and after FindPackages call
pkgs := packageManager.FindPackages;
// Get the Iterator from the IIterable
pIter := pkgs.First; // pIter is now $00000003...
hasCurrent := pIter.HasCurrent; // obviously Access Violation
while hasCurrent do
begin
package := pIter.Current;
packageId := package.Id;
hasCurrent := pIter.MoveNext;
end;
end;
finally
RoUninitialize;
end;
end.
I'm not looking for read registry or set NTFS access in %programfiles\WindowsApps solution, because this is just a base for project that functionality will be expanded. Being able to list all packages in elegant & Microsoft designed way would be a great start.
Related
The good (and bad) old Delphi taught us the "classic" way of building application because of the way we write code "behind" the IDE.
Based on this paradigm, I built some time ago a library that allows me to save/load the GUI to INI file with a single line of code.
LoadForm(FormName)
BAM! That's it! No more INI files!
The library only saves "relevant" properties. For example, for a TCheckBox it saves only its Checked property not also its Color or Top/Left. But it is more than enough.
Saving the form has no issues. However, I have "problems" during app initialization. They are not quite problems, but the initialization code is not that nice/elegant.
For example when I assign Checkbox1.Checked := True it will trigger the OnClick event (supposing that the checkbox was unchecked at design time). But assigning a False value (naturally) will not trigger the OnClick event.
Therefore, I have to manually call CheckBox1Click(Sender) after SaveForm(FormName) to make sure that whatever code is in CheckBox1Click, it gets initialized. But this raises another problem. The CheckBox1Click(Sender) might be called twice during application start up (once by SaveForm and once my me, manually).
Of course, in theory the logic of the program should be put in individual objects and separated from the GUI. But even if we do this, the initialization problem remains. You load the properties of the object from disk and you assign them to the GUI. When you set whatever value you have in your object to Checkbox1, it will (or not) call CheckBox1Click(Sender) which will set the value back into the object.
On app startup:
procedure TForm.FormCreate (Sender: TObject);
begin
LogicObject.Load(File); // load app logic
Checkbox1.Checked := LogicObject.Checked; // assign object to GUI
end;
procedure TForm.CheckBox1Click(Sender: TObject);
begin
LogicObject.Checked := Checkbox1.Checked;
end;
Probably the solution involves writing stuff like this for EVERY control on the form:
OldEvent := CheckBox1.OnClick;
CheckBox1.OnClick := Nil;
CheckBox1.Checked := something;
CheckBox1.OnClick := OldEvent;
Not elegant.
Question:
How do you solve this specific problem OR what is your approach when saving/restoring your GUI to/from disk?
This is one of the things which botthered me in some components from the beginning. What I know the are 3 options, except separating GUI and the business logic as #David said, which is not always an option.
As you wrote above, always unassign the events so they don't get triggered
Use non-triggered events such as OnMouseDown or OnMouseUp
Or a similar solution that I use and I think is the most elegant
Create a global variable FormPreparing which is set during initialization and check its value at the beginning of the events like below.
procedure TForm.FormCreate (Sender: TObject);
begin
FormPreparing := True;
try
LogicObject.Load(File); // load app logic
Checkbox1.Checked := LogicObject.Checked; // assign object to GUI
finally
FormPreparing := False;
end;
end;
procedure TForm.CheckBox1Click(Sender: TObject);
begin
if FormPreparing then
Exit;
LogicObject.Checked := Checkbox1.Checked;
end;
I've written program in Lazarus Pascal. It was entirely written on Mac, then I've switched to windows, recompiled it (recompiled .dylib to .dll, recompiled and installed custom component), and it runs, but when I try to do anything, it throws error:
While on debug mode:
Project project1 raised exception class 'External:SIGSEGV'. At address 772CD4F1
Running .exe file :
Access violation.
// EDIT
I've noticed that it has a problem with that part of code, in particular at ListView.Clear command:
procedure AddressList.updateView(ListView : TListView);
var
element : ListElement;
newItem : TListItem;
begin
ListView.Clear;
element := first;
if element = nil then
exit;
while element <> nil do
begin
newItem := ListView.Items.Add;
newItem.Caption := element^.name;
newItem.SubItems.Add(element^.surname);
newItem.SubItems.Add(element^.address);
newItem.SubItems.Add(formatNumber(element^.phoneNumber));
element := element^.next;
end;
end;
How is that possible, what could I do wrong?
In Free Pascal Classe instances are always implicit pointers.
It seems that for some reason your ListView doesn't contain a properly created class instance. The "pointer" ListView points to wherever. When the class method Cleartries to access the data you get a segmentation fault.
A watch of ListView should show either garbage data or <invalid>.
I currently am in the process of porting several Windows desktop applications to a single web site.
The current setup includes several SQL server backend databases, configured with Windows Authentication (SSPI) only, and every user/group/role has specific rights on specific objects. Which is convenient, because the application layer doesn't have to implement any access control.
I'd like to keep it the same way with the web server, an Apache on a Windows machine. But every connection to the databases is being made using Apache's account. That's understandable and expected, in fact Apache is deliberately given access to public data, to be able to deliver public content.
But in case a domain user logs in (the login process is already implemented) I'd like the Apache process that handles the request to impersonate that user, and thus act as them during the whole request.
At first, I tried php's fastcgi.impersonate trick, using IIS as the web server. But I eventually gave up on that, mainly because (1) we had to port to Apache anyway and (2) it was php-specific, and it turned out we should be targeting the web server as a whole...
So I redirected my search to Apache modules. Months of research gave no fruits, other than mod_auth_sspi and the like, which apparently isn't what I'm looking for (authentication and impersonation are two different things).
Finally I decided to make my own module. Most of the "101" examples I could find are written in C, but I managed to find 2-3 ones in Lazarus/FPC, which is what I've been using for quite a while now, but never for such a task.
I know I have to build a .dll project, I know (more or less) what units to use and I know functions like LogonUser() and ImpersonateLoggedOnUser() should be in my toolbox.
Has anyone done anything similar? Can anyone point me to the right direction?
An example would be appreciated, even if it's a simple proof of concept. This question is far from asking for a final, definitive solution.
I eventually came up with the following:
library mod_winimpersonate;
{$mode objfpc}{$H+}
uses SysUtils, Windows, httpd, apr, Classes;
function DefaultHandler(r: Prequest_rec): Integer; cdecl;
Var
cookies:TStringList;
logindata,username,password:String;
p:Integer;
begin
RevertToSelf;
cookies:=TStringList.Create;
cookies.Delimiter:=';';
cookies.DelimitedText:=apr_table_get(r^.headers_in,'COOKIE');
logindata:=URLDecode(cookies.Values['WinImpersonate']);
If Length(logindata)>0 then
Begin
p:=Pos(':',logindata);
username:=LeftStr(logindata,p-1);
password:=RightStr(logindata,Length(logindata)-p);
ChangeLoggedInUser(username,password,'');
End;
Result:=DECLINED;
end;
procedure RegisterHooks(p: Papr_pool_t); cdecl;
begin
ap_hook_handler(#DefaultHandler, nil, nil, APR_HOOK_REALLY_FIRST);
end;
var
TheModule: module;
exports TheModule name 'winimpersonate_module';
begin
FillChar(TheModule, sizeof(TheModule), 0);
STANDARD20_MODULE_STUFF(TheModule);
with TheModule do
begin
name := 'mod_winimpersonate.dll';
register_hooks := #RegisterHooks;
end;
end.
This is by no means a final solution, but it's a start. The logic is the following:
Revert to the Apache account. This is a must, in case we are using a recycled Apache thread that previously impersonate someone else.
Retrieve the user's credentials from a cookie named 'WinImpersonate', in the form of username:password. This needs more work (maybe encrypt the credentials, or store them in a safe(?) place up on the server or something even more secure)
Impersonate the user, with the help of the windows unit.
Return DECLINED, so Apache knows we didn't handle the request, and it should continue asking modules for the right handler.
There are many concerns to be addressed, in order for this to achieve a decent security level. Among others, the protection of the credentials, and the browser cache. But as I said, it's a start, as well as a proof of concept.
You'll notice that there are two utility functions missing from the above listing:
URLDecode decodes a url-encoded string:
// Convert URLEncoded string to utf8 string
function URLDecode(const s: String): String;
var
sAnsi: String;
sUtf8: String;
sWide: WideString;
i, len: Cardinal;
ESC: string[2];
CharCode: integer;
c: char;
begin
sAnsi := PChar(s);
SetLength(sUtf8, Length(sAnsi));
i := 1;
len := 1;
while (i <= Cardinal(Length(sAnsi))) do
begin
if (sAnsi[i] <> '%') then
begin
if (sAnsi[i] = '+') then c := ' ' else c := sAnsi[i];
sUtf8[len] := c;
Inc(len);
end
else
begin
Inc(i);
ESC := Copy(sAnsi, i, 2);
Inc(i, 1);
try
CharCode := StrToInt('$' + ESC);
c := Char(CharCode);
sUtf8[len] := c;
Inc(len);
except
end;
end;
Inc(i);
end;
Dec(len);
SetLength(sUtf8, len);
sWide := UTF8Decode(sUtf8);
len := Length(sWide);
Result := sWide;
end;
ChangeLoggedInUser tries to login the user using the credentials supplied, and upon success it tries to impersonate him:
Function ChangeLoggedInUser(username, password, domain: string):Boolean;
var
creds: Cardinal;
begin
Result:=False;
try
if LogonUser(PChar(username)
,PChar(domain)
,PChar(password)
,LOGON32_LOGON_NETWORK_CLEARTEXT
,LOGON32_PROVIDER_DEFAULT
,creds
) then
begin
ImpersonateLoggedOnUser(creds);
Result:=True;
end;
finally
//wipe the memory for security
FillChar(username,SizeOf(username),#0);
FillChar(password,SizeOf(username),#0);
FillChar(domain,SizeOf(username),#0);
end; //try-finally
end;
Hope someone finds this useful. Comments are more than welcome.
I'm trying to write a plugin system for my application based on jvPlugin. I create forms in the plugin dll, then reparent them into DevExpress docking controls. At first sight, it seems to work. The problem is that none of the controls on the dll forms ever receive focus. Also, when clicking on controls like TSplitter, a "Control 'xxx' has no parent window" exception is raised.
Here's how I'm doing it (condensed version).
The plugin host implements an IPluginHost interface
IPluginHost = interface
['{C0416F76-6824-45E7-8819-414AB8F39E19}']
function AddDockingForm(AForm: TForm): TObject;
function GetParentApplicationHandle: THandle;
end;
The plugin implments an IMyPlugin interface
IMyPlugin = interface
['{E5574F27-3130-4EB8-A8F4-F709422BB549}']
procedure AddUIComponents;
end;
The following event is called when the plugin is initialised:
procedure TMyPlugin.JvPlugInInitialize(Sender: TObject; var AllowLoad: Boolean);
var
RealApplicationHandle: THandle;
begin
if Supports(HostApplication.MainForm, IPluginHost, FPluginHost) then
begin
RealApplicationHandle := Application.Handle;
Application.Handle := FPluginHost.GetParentApplicationHandle; // Returns Application.Handle from the host application
try
FMyPluginForm:= TMyPluginForm.Create(Application); // Plugin host app owns the form
finally
Application.Handle := RealApplicationHandle;
end;
end;
end;
When the plugin host has loaded I call IMyPlugin.AddUIComponents in my plugin. It's implemented like this:
procedure TMyPlugin.AddUIComponents;
begin
// Add the docking form
FPluginHost.AddDockingForm(FMyPluginForm);
end;
AddDockingForm is implemented in the host like this:
function TfrmMyPluginHost.AddDockingForm(AForm: TForm): TObject;
var
DockPanel: TdxDockPanel;
begin
// Create a new dockpanel
DockPanel := TdxDockPanel.Create(Self);
DockPanel.Name := DPName;
DockPanel.Height := AForm.Height;
DockPanel.DockTo(dxDockSite1, dtBottom, 0);
DockPanel.AutoHide := TRUE;
// Rename the dock panel and parent the plugin
DockPanel.Caption := AForm.Caption;
DockPanel.Tag := Integer(AForm);
AForm.Parent := DockPanel;
AForm.BorderStyle := bsNone;
AForm.Align := alClient;
AForm.Show;
FDockedPluginFormList.Add(AForm);
Result := DockPanel;
end;
If I run the following function on any of the controls on the plugin form I see a list going all the way back to my host's main form. Yet TSplitters tell me they have no parent window. How can this be?
function TfrmMyPlugin.GetParents(WC: TWinControl): String;
begin
if (WC <> nil) and (WC is TWinControl) then
Result := WC.Name + ' [' + WC.ClassName + '] - ' + GetParents(WC.Parent);
end;
I must be missing something somewhere. Anybody got any good ideas?
Build both the plugin and the host application with runtime packages.
Without using runtime packages, the DLL uses a distinct copy of the RTL, VCL and any used units. For example, the DLL's TForm class is not the same as the host's TForm class (is operator fails across the host/DLL boundary and therefore a DLL control will not recognize host parent form as a valid instance of TForm), global variables like Application, Mouse, Screen are separate instances, you have two copies of RTTI, etc, etc.
The VCL was simply not designed to be used like this.
With runtime packages on, all the problems are solved and the plugin (either a runtime package itself or a DLL built with runtime packages) can integrate seamlessly with the host application using runtime packages.
We made it work using "runtime packages" adding "vcl,rtl,ourownpckg"
Where ourownpckg is a our-own-package created with all the DX dependencies and some other that we use across exe-plugins, including JVCL ones.
The three packages must be shiped along the exe
Hope it helps
What is the best way to determine if the flash ocx is installed in Innosetup (or any installer for that matter). I don't want to attempt to install it myself, I will simply force the user to go to the flash site and install, I just want to make sure that the flash.ocx (version 9+) is installed.
Is it enough to check for HKEY_CLASSES_ROOT\ShockwaveFlash.ShockwaveFlash and check that CurVer >= 9? Is there a better way to test for this?
Add a function in the code section to check whether you can create an instance of the Flash control, like so:
function IsFlashInstalled(): boolean;
var
V: Variant;
begin
try
V := CreateOleObject('ShockwaveFlash.ShockwaveFlash.9');
Result := True;
except
Result := False;
end;
end;
Check out the various examples in the Inno Setup package on how to use your own function to show a message box to the user, cancel the installation, open the Flash site in the default browser or whatever you want to do.
Easy way without Try/Except
function IsFlashInstalled: Boolean;
var ClassID : TCLSID;
begin
Result := Succeeded(CLSIDFromProgID('ShockwaveFlash.ShockwaveFlash', ClassID));//Use CreateComObject() instead...
end;