Get User Principal Name (UPN) In InnoSetup Installer? - windows

Within the InitializeSetup() function among other actions, when the installer is ran, I would like the installer to retrieve the current UPN. The UserName variable is not sufficient enough. I have also tried methods discussed here utilizing the WTSQuerySessionInformation() function but they don't seem to return what I am looking for. Depending on the organization and setting the UPN should often return some sort of an email address which I am looking for. Can someone shed some light on how to return the full UPN value as a string? Thank you.
EDIT:
I have also tried the GetUserNameExW() function passing in value 8 as an input which refers to UserNamePrincipal, however I am returning an empty value it seems.
function GetUserNameExW(NameFormat: Integer; lpNameBuffer: string; var nSize: DWORD): Boolean;
external 'GetUserNameExW#secur32.dll stdcall';
var
NumChars: DWORD;
OutStr: string;
name: string;
begin
SetLength(OutStr, NumChars);
GetUserNameExW(8, OutStr, NumChars);
name := Copy(OutStr,1,NumChars);

The correct code to call GetUserNameExW to get the current user's userPrincipalName (UPN) attribute would look like this:
function GetUserNameExW(NameFormat: Integer; lpNameBuffer: string; var nSize: DWORD): Boolean;
external 'GetUserNameExW#secur32.dll stdcall';
function GetUserPrincipalName(): string;
var
NumChars: DWORD;
OutStr: string;
begin
result := '';
NumChars := 0;
if (not GetUserNameExW(8, '', NumChars)) and (DLLGetLastError() = 234) then
begin
SetLength(OutStr, NumChars);
if GetUserNameExW(8, OutStr, NumChars) then
result := Copy(OutStr, 1, NumChars);
end;
end;
The value 8 for the NameFormat parameter corresponds to NameUserPrincipal in the EXTENDED_NAME_FORMAT enumeration, and the value 234 is API value ERROR_MORE_DATA.
However--as I pointed out in a comment--if you are looking for an email address, this would not be the code to do that because userPrincipalName (UPN) is a separate user attribute (in fact, in many, if not most organizations, the UPN is different from the user's email address). Also, if you are assuming the UPN to be the same as one of the user's email email addresses, this would also be an incorrect assumption, as the UPN's value is very often not in the list of valid email addresses for a user.
The point is that if you are looking for a reliable way to get a valid email address for a user, the UPN is not going give you one.

I have managed to the solve this issue myself but still not 100% sure on why my previous iteration resulted in odd behavior.
Essentially, I had to add an if check before:
if GetUserNameExW(8, OutStr, NumChars) then

Related

Delphi - dbGo/Oracle method GetFieldNames returns no data

Delphi Rio - I am just starting to learn ADO, specifically the dbGo components, connected to a local Oracle RDBMS (Oracle 12.2 64 bit). I am able to connect, issue simple queries, etc. I found the method TADOConnection.GetFieldNames, and I am experimenting with it. I am not able to get it to work. Here is my code...
procedure TForm1.BitBtn1Click(Sender: TObject);
var
S1 : TStringList;
begin
S1 := TStringList.Create;
ADO1.Connected := True;
ADO1.GetFieldNames('EGR.ACCOUNTS', S1);
//ADO1.GetTableNames(S1, False);
ShowMessage(IntToStr(S1.Count));
S1.Free;
end;
I have tried with and without the Schema name, yet S1.Count always returns 0. The GetTableNames function works fine. If I go into SQL*Plus and query, I see the appropriate data
select count(*) from EGR.ACCOUNTS;
So I know my SCHEMA.TABLENAME is correct. What am I doing wrong?
To get hand on a data field by its name, you shall use this code:
var
FieldEgrAccount : TField;
begin
FieldEgrAccount := AdoQuery1.FieldByName('SomeFieldName');
Memo1.Lines.Add(FieldEgrAccount.AsString);
end;
If you really need to have all field names, use this code:
var
Names : TStringList;
begin
Names := TStringList.Create;
try
AdoQuery1.GetFieldNames(Names);
// Do something with the field names
finally
Names.Free;
end;
end;
It is much faster to use one TField per field, get it once and reuse it as many times as needed (Make the variable fields of the form or datamodule class). FieldByName is relatively costly because it has to scan the list of field names.
You need to assign it to the items property of the stringlist which is of type TStrings.
procedure GetFieldNames(const TableName: string; List: TStrings);

Getting list of pointing devices in Windows (pascal)

I'm using Lazarus/FPC and I'm looking for a way to get a list of pointing devices in Windows - and then ultimately to be able to disable and enable particular devices.
A bit of Googling turned up this on MSDN and this on the FreePascal wiki.
These look like a good starting point but unfortunately I'm falling at the first hurdle... I can't figure out how to create the manager object that is referred to in the example.
The MSDN example is (C#):
private void PopulatePointers(TreeView tvDevices)
{
//Add "Pointer Devices" node to TreeView
TreeNode pointerNode = new TreeNode("Pointer Devices");
tvInputDevices.Nodes.Add(pointerNode);
//Populate Attached Mouse/Pointing Devices
foreach(DeviceInstance di in
Manager.GetDevices(DeviceClass.Pointer,EnumDevicesFlags.AttachedOnly))
{
//Get device name
TreeNode nameNode = new TreeNode(di.InstanceName);
nameNode.Tag = di;
TreeNode guidNode = new TreeNode(
"Guid = " + di.InstanceGuid);
//Add nodes
nameNode.Nodes.Add(guidNode);
pointerNode.Nodes.Add(nameNode);
}
}
Which I have partially translated to Pascal as:
uses windows, DirectInput;
procedure getPointingDevices();
begin
for pointingDevice in Manager.GetDevices(DeviceType.Keyboard,EnumDevicesFlags.AttachedOnly) do
begin
devicesTree.Items.AddChild(devicesTree.Items.TopLvlItems[0],pointingDevice.InstanceName);
end;
devicesTree.Items.TopLvlItems[0].Expand(true);
end;
and I have included DirectInput.pas, DirectX.inc, DXTypes.pas, Jedi.inc, Xinput.pas (some of which may not actually be needed, I'll work that out later) in the project.
Obviously I need to create the Manager object to be able to access its methods, but I have no idea how to do that from the documentation I've read so far.
What you are looking for is the DirectInput IDirectInput8 COM interface.
To enumerate input devices, obtain the IDirectInput8 interface using the DirectInput8Create() function, and then use its EnumDevices() or EnumDevicesBySemantics() method. For example:
uses
Windows, DirectInput;
function MyEnumCallback(lpddi: LPCDIDEVICEINSTANCE; pvRef: Pointer): BOOL; stdcall;
var
Tree: TTreeView;
begin
Tree := TTreeView(pvRef);
Tree.Items.AddChild(Tree.Items.TopLvlItems[0], lpddi.tszInstanceName);
end;
procedure getPointingDevices;
var
DI: IDirectInput8;
begin
OleCheck(DirectInput8Create(HInstance, DIRECTINPUT_VERSION, IDirectInput8, #DI, nil));
OleCheck(DI.EnumDevices(DI8DEVCLASS_POINTER, #MyEnumCallback, devicesTree, DIEDFL_ATTACHEDONLY));
devicesTree.Items.TopLvlItems[0].Expand(true);
end;

Find if a given SID belongs to a group identified by SID

I'm writing a windows service that performs operations according to different rules, one of which is based on the requesting user identity.
It thus receives the requesting user SID and then compares it to its internal list of SIDs to decide what operation it will perform. Using the EqualSID API function makes this very easy.
However, I am now faced with the situation where some SIDs in the service list are group SIDs and not user SIDs.
This means that I have to find a way to test if the received SID is either equal to the one in the list or belongs to the group that is represented by the SID in the list.
I looked around to see what APIs would be available and found about CheckTokenMembership which requires a token handle. That's where I'm a bit lost because as the service is not necessarily located on the same machine, I can't seem to find a way to create a valid token handle from the SID that I have received.
The service itself runs under the default "NT Service" account and I would prefer if it could stay this way.
What API would you suggest I use?
The target language is Delphi but I can understand examples in plain C.
Well, after looking around at various other things I finally managed to find a way to achieve this. In short, the answer is Active Directory Service Interfaces also known as ADSI
To give a bit more details should someone else be looking at that, here is a series of steps to achieve this in Delphi:
Import the Active DS Type Library set of objects into Delphi. This will create the ActiveDs_TLB unit with all the necessary interfaces
Declare AdsGetObject like so
function ADsGetObject(lpszPathName: WideString; const riid: TGUID; out ppObject): HRESULT; safecall;
Retrieve an IADSUser instance with the SID syntax:
var
SIDUser: IADSUser;
User: IADSUser;
SIDGroup: IADSGroup;
Group: IADSGroup;
begin
// Bind using the SID
AdsGetObject('LDAP://<SID=S-1-5-7>', IADSUser, SIDUser);
// rebind using the distinguished name as suggested by MSDN
AdsGetObject('LDAP://' + SIDUser.Get('distinguishedName'), IADSUser, User);
// Use the User instance
ShowMessage(User.FullName);
// Same method for group
AdsGetObject('LDAP://<SID=S-1-5-32-545>', IADSGroup, SIDGroup);
AdsGetObject('LDAP://' + SIDGroup.Get('distinguishedName'), IADSGroup, Group);
// IsMember does not seem to work with LDAP
// https://groups.google.com/forum/#!topic/microsoft.public.adsi.general/2d-e4HPXGfA
// http://www.rlmueller.net/Programs/IsMember4.txt
// if Group.IsMember(User.ADsPath) then
if AdsIsMember(User, 'S-1-5-32-545') then
ShowMessage('InGroup');
end;
As you can see, one would want to use the IsMember method of IADSGroup but clearly it does not work because it should return True in the above case (S-1-5-32-545 is the world group).
So as suggested by the link give in the comment, I wrote my own IsMember like so:
function ADsIsMember(const User: IADSUser; const GroupSID: string): Boolean;
const
TokenGroupsId = 'tokenGroups';
var
PropNames: array of OleVariant;
TokenGroups: OleVariant;
TokenGroupLow: Integer;
TokenGroupHigh: Integer;
TokenGroupIndex: Integer;
SIDBytes: array of Byte;
SIDAsString: PChar;
begin
Result := False;
SetLength(PropNames, 1);
PropNames[0] := TokenGroupsId;
User.GetInfoEx(PropNames, 0);
TokenGroups := User.Get(TokenGroupsId);
TokenGroupLow := VarArrayLowBound(TokenGroups, 1);
TokenGroupHigh := VarArrayHighBound(TokenGroups, 1);
for TokenGroupIndex := TokenGroupLow to TokenGroupHigh do
begin
SIDBytes := TokenGroups[TokenGroupIndex];
ConvertSidToStringSid(#SIDBytes[0], SIDAsString);
if GroupSID = SIDAsString then
Exit(True);
end;
end;
With all this, I can now check if a given SID belongs to a group defined by its SID.

How can I call an Oracle function from Delphi?

I created a function in Oracle that inserts records in specific tables and return an output according to what occurs within the function. e.g (ins_rec return number)
How do I call this function and see its output in Delphi?
I got a reply (with all my thanks) for sql plus but I need to know how can I do this in Delphi
Just pass the user defined function as column name in the query and it will work.
Example:
Var
RetValue: Integer;
begin
Query1.Clear;
Query1.Sql.Text := 'Select MyFunction(Param1) FunRetValue from dual';
Query1.Open;
if not Query1.Eof then
begin
RetValue := Query1.FieldByName('FunRetValue').AsInteger;
end;
end;
How to accomplish it may depend on what DB access library you use (BDE? dbExpress? ADO? others), some may offer a "stored procedure" component that may work with functions as well.
A general approach it to use an anonymous PL/SQL block to call the function (and a parameter to read the return value), PL/SQL resembles Pascal a lot...:
Qry.SQL.Clear;
Qry.SQL.Add('BEGIN');
Qry.SQL.Add(' :Rez := ins_rec;');
Qry.SQL.Add('END;');
// Set the parameter type here...
...
Qry.ExecSQL;
...
ReturnValue := Qry.ParamByName('Rez').Value;
I would not have used a function, though, but a stored procedure with an OUT value. Moreover, Oracle offers packages that are a very nice way to organize procedure and functions, and they also offer useful features like session variables and initialization/finalization sections... very much alike a Delphi unit.
we run an Oracle stored procedure using this code that utilizes the BDE (I know please don't bash because we used the BDE!)
Try
DMod.Database1.Connected:= False;
DMod.Database1.Connected:= True;
with DMod.StoredProc1 do
begin
Active:= False;
ParamByName('V_CASE_IN').AsString:= Trim(strCaseNum);
ParamByName('V_WARRANT_IN').AsString:= strWarrantNum;
ParamByName('V_METRO_COMMENTS').AsString:= strComment;
Prepare;
ExecProc;
Result:= ParamByName('Result').AsString;
end;
Except

Component disabling and enabling at runtime in Delphi 2K9. Weird problem

Here is code:
procedure DisableContrlOL(const cArray : array of string; ReEnable : boolean = False);
// can be called from VKP / RAW / Generation clicks
var
AComponent: TComponent;
CompListDis, CompListEna : TStringList;
begin
CompListDis := TStringList.Create;
CompListEna := TStringList.Create;
for i := Low(cArray) to High(cArray) do begin
AComponent := FindComponent(cArray[i]);
if Assigned(AComponent) then
if (AComponent is TControl) then begin
if TControl(AComponent).Enabled then
CompListEna.Add(TControl(AComponent).Name)
else
CompListDis.Add(TControl(AComponent).Name);
ShowMessage(TControl(AComponent).Name);
if ReEnable then begin // if reenabling needed, then all whi
if not TControl(AComponent).Enabled then
TControl(AComponent).Enabled := True;
end else if (TControl(AComponent).Enabled) then
TControl(AComponent).Enabled := False;
end;
end;
end;
I think no more explanations are needed.
The ShowMessage correctly shows name of each component, but nothing is added in StringLists. Why?
UPDATE: As question has gone pretty wild, I did confirm answer, which a bit helped me.
I understand that I did write things pretty unclear, but I am very limited, because these code lines is part of commercial project, and my hobby and heart thing. The main problem was found already 6h ago, but Rob just wanted to extend this whole question :D No, no offense, mate, it's OK. I am happy to receive so willing and helpful posts. Thanks again.
How do you know that nothing is added to the lists? You create them in this code and the only references to them are in local variables. The objects are leaked when this function returns, so you never actually use the lists anywhere.
You've said you have code for "modular testing." Since that code isn't here, I must assume the code is not part of this function. But if you have external code that's supposed to check the contents of the lists, then the lists can't be just local variables. No other code can access them. You need to either return those lists or accept lists from outside that you then fill. Here's an example of the latter:
procedure DisableContrlOL(const cArray: array of string;
Reenable: Boolean
CompListDis, CompListEna: TStrings);
// can be called from VKP / RAW / Generation clicks
var
AComponent: TComponent;
AControl: TControl;
i: Integer;
begin
for i := Low(cArray) to High(cArray) do begin
AComponent := FindComponent(cArray[i]);
if not Assigned(AComponent) or not (AComponent is TControl) then
continue;
AControl := TControl(AComponent);
if AControl.Enabled then
CompListEna.Add(AControl.Name)
else
CompListDis.Add(AControl.Name);
ShowMessage(AControl.Name);
AControl.Enabled := Reenable;
end;
end;
The caller of this function will need to provide a TStrings descendant for each list. They could be TStringList, or they could be other descendants, such as TMemo.Lines, so you can directly observe their contents in your program. (They can't be just TStrings, though, since that's an abstract class.)
As you can see, I made some other changes to your code. All your code using the Reenable parameter can be simplified to a single statement. That's because enabling a control that's already enabled, and disabling a control that's already disabled, are no-ops.
Also, Name is a public property of TComponent. You don't need to type-cast to TControl before reading that property, but since you're type-casting so often elsewhere, it made sense to introduce a new variable to hold the type-casted TControl value, and that can make your code easier to read. Easier-to-read code is easier-to-understand code, and that makes it easier to debug.
Emphasizing that this is largely based on Rob's excellent suggestions, it looks as though you could simplify the code to:
procedure DisableContrlOL(const cArray : array of string;
ReEnable : boolean = False);
var
AComponent: TComponent;
begin
for i := Low(cArray) to High(cArray) do
begin
AComponent := FindComponent(cArray[i]);
if Assigned(AComponent) then
if (AComponent is TControl) then
begin
ShowMessage(TControl(AComponent).Name);
TControl(AComponent).Enabled := ReEnable;
end;
end;
end;
Not clear what the stringlists were for, since their contents were lost when execution left the scope of this procedure. If you want to return them, you should create and free them in the calling code.
That sure looks like it ought to work. This is the sort of thing that the debugger can probably help with more than we can here.
Try breaking the problematic line down into multiple lines, like so:
if TControl(AComponent).Enabled then
CompListEna.Add(TControl(AComponent).Name)
else CompListDis.Add(TControl(AComponent).Name);
Rebuild with the "Use Debug DCUs" option on, and place a breakpoint on the if statement. Then use F7 to trace your way through the logic and see what's going on.

Resources