[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 684
  • Last Modified:

Active Directory problems.

I have this code. When I run the code and in, the While not, I don't get any errors 1st time I pass line 15, but 2nd time, it jumps to line 28.

Should I release the object some how everytime I have done something with the object?

procedure TFrmMain.Button4Click(Sender: TObject);
var i, iDays : integer; adUsr : IADsUser; sDays : string;
begin
  memo1.Lines.Clear;
  ADOQuery1.Close;
  try
    ADOQuery1.SQL.Clear;
    ADOQuery1.SQL.Text := 'SELECT samAccountName, CN FROM '+ Quotedstr('LDAP://OU=USERS,' + edit2.Text) + ' WHERE objectClass='+ Quotedstr('user') + ' ORDER by CN';
    ADOQuery1.Open;
    while not ADOQuery1.Eof do
    begin
      ADsGetObject('LDAP://CN='+ADOQuery1.FieldByName('CN').AsString+',OU=USERS,' + Edit2.Text, IADsUser, adusr);
      label8.Caption := datetostr(adusr.LastLogin);
      if adusr.AccountDisabled then
      begin
        sdays := InttoStr(MonthsBetween(Date, ADUsr.LastLogin));
        idays := StrToInt(sDays);
        if IDays > 0 then
        begin
          if not renamefile('\\seluws0187\home_dkso\'+ADOQuery1.FieldByName('samAccountName').AsString,'\\seluws0187\home_dkso\_'+ADOQuery1.FieldByName('samAccountName').AsString) then
          begin
            Memo1.Lines.Add(ADOQuery1.FieldByName('samAccountName').AsString);
          end;
        end;
      end;
      ADOQuery1.Next;
    end;
  finally
    ADOQuery1.Close;
     label7.Caption := inttostr(memo1.Lines.Count);
  end;
end;

Open in new window

0
QC20N
Asked:
QC20N
  • 30
  • 22
  • 2
1 Solution
 
MerijnBSr. Software EngineerCommented:
The line numbers you mention, are those the line numbers in the code snippet in your post (line 15 = begin, line 28 = finally)
0
 
Geert GruwezOracle dbaCommented:
add this:
if adusr <> nil then
begin



ADsGetObject('LDAP://CN='+ADOQuery1.FieldByName('CN').AsString+',OU=USERS,' + Edit2.Text, IADsUser, adusr);
if adusr <> nil then
begin
      label8.Caption := datetostr(adusr.LastLogin);
      if adusr.AccountDisabled then
0
 
QC20NAuthor Commented:
No, the line numbers is 13 and 26.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
QC20NAuthor Commented:
Geert Gruwez. It does the same thing.

My error is OLE Error 8000500D
0
 
QC20NAuthor Commented:
Ahh, crap. No, it is #13 and #29.

My mistake. Sorry.
0
 
QC20NAuthor Commented:
You have any input?
0
 
QC20NAuthor Commented:
.As long I don't use:
adusr.Lastlogin

I don't get any errors.

Very annoying. :)
0
 
Geert GruwezOracle dbaCommented:
only use lastlogin once

var dt: TDateTime;

      label8.Caption := '';
      if adusr.AccountDisabled then
      begin
        dt := ADUsr.LastLogin;
        label8.Caption := DateToStr(dt);
        sdays := InttoStr(MonthsBetween(Date, dt));
        idays := StrToInt(sDays);
        if IDays > 0 then
        begin
0
 
QC20NAuthor Commented:
But I want to check it for every user in our environment, that is disabled.

        label8.Caption := '';
        if adusr.AccountDisabled then
        begin
          dt := ADUsr.LastLogin;
          label8.Caption := DateToStr(dt);
          sdays := InttoStr(MonthsBetween(Date, dt));
          idays := StrToInt(sDays);
          if IDays > 0 then
          begin

Open in new window

0
 
Geert GruwezOracle dbaCommented:
you only put it in the caption for label8
you aren't doing any checking at all for disabled accounts ...

it's not logical why you want to display a date in a caption and not do anything with it
it gets written over for the next non disabled user any way

i know it's only a test scenario you gave, but that's all we see to work with
0
 
Geert GruwezOracle dbaCommented:
next to that, you don't have any
Application.ProcessMessages calls nor any label8.update
so while doing the loop windows will not have any idle time to draw the caption anyway
so you won't see the label8.caption change either

it may look nice in code, but you just don't see anything on screen changing when running
unless you have 2 monitors and are debugging
0
 
QC20NAuthor Commented:
All you see is only test scenario.
The reason for displaying date in Caption is just when I debug. I'm not going to use in the finished project.

Believe me, I have tried differents solution to check when they were disabled and when the user haven't login for more than 1 month.

The code is the end code. But nomatter what I do I get an error everytime a pass ADUser.Lastlogin.
And that is why you have seen the test scenario, cause I have tried different solutions to see if I did something wrong.
procedure TFrmMain.Button4Click(Sender: TObject);
var i, iDays : integer; adUsr : IADsUser; sDays : string;
begin
  memo1.Lines.Clear;
  ADOQuery1.Close;
  try
    ADOQuery1.SQL.Clear;
    ADOQuery1.SQL.Text := 'SELECT samAccountName, CN FROM '+ Quotedstr('LDAP://OU=USERS,' + edit2.Text) + ' WHERE objectClass='+ Quotedstr('user') + ' ORDER by CN';
    ADOQuery1.Open;
    while not ADOQuery1.Eof do
    begin
      ADsGetObject('LDAP://CN='+ADOQuery1.FieldByName('CN').AsString+',OU=USERS,' + Edit2.Text, IADsUser, adusr);
      if (adusr.AccountDisabled) and (MonthsBetween(Date, ADUsr.LastLogin) > 0) then
      begin
        if not renamefile('\\seluws0187\home_dkso\'+ADOQuery1.FieldByName('samAccountName').AsString,'\\seluws0187\home_dkso\_'+ADOQuery1.FieldByName('samAccountName').AsString) then
        begin
          Memo1.Lines.Add(ADOQuery1.FieldByName('samAccountName').AsString);
        end;
      end;
      ADOQuery1.Next;
    end;
  finally
    ADOQuery1.Close;
  end;
end;

Open in new window

0
 
QC20NAuthor Commented:
I get an error everytime 2nd pass ADUser.lastlogin. I mean, 1st it is ok, but 2nd time I get the error.
0
 
Geert GruwezOracle dbaCommented:
what if this isn't filled in ?

try
  dt := aduser.LastLogin;
except
  dt := 0;
end;

then use if dt > 0

does it pass for any next records ?
0
 
Geert GruwezOracle dbaCommented:
remember that when using AD on XP you have a memory leak

it only works as it should on 2003 and up
0
 
QC20NAuthor Commented:
I tried what you have been written. And yes, it pass for some of the disabled users.
        if adusr.AccountDisabled {and (inttostr(MonthsBetween(date,adusr.lastlogin)) > '0')} then
        begin
          try
            dt := ADUsr.LastLogin;
          except
            dt := 0;
          end;
          label8.Caption := DateToStr(dt);

Open in new window

0
 
QC20NAuthor Commented:
You have any idea what cause that?
0
 
Geert GruwezOracle dbaCommented:
when it is null you will get an error (or not set in AD)


0
 
QC20NAuthor Commented:
But isen't Lastlogin set all the time in AD?
0
 
Geert GruwezOracle dbaCommented:
obviously not
create a new user and don't login with it yet, then run your code ...
0
 
QC20NAuthor Commented:
I'll raise the points, for a solution or might an explanation why this dosen't work.
0
 
MerijnBSr. Software EngineerCommented:
> create a new user and don't login with it yet, then run your code

Need more explanation than that?
0
 
Geert GruwezOracle dbaCommented:
not to offend, but i allready showed you the solution,
you seem not to grasp it ...

only read the LastLogin once in a try except into a variable
and use that variable

try
  dt := aduser.LastLogin;
except
  dt := 0;
end;

raise points ?
admit it, your point giving is ridicolously low for the last questions ...
0
 
QC20NAuthor Commented:
:) I was writeing my comment before I saw your input. :) I will of course try that.
0
 
Geert GruwezOracle dbaCommented:
the error seems to happen for every item read from active directory which is not set

here is my code:
hope you get the idea ...
function TImportThread.ReadProperty(Obj: IAds; propName: string = 'businessRoles'): string;
var
  prop, subprop: OleVariant;
  Temp: string;
  I, J, m, n: integer;
begin
  Temp := '';
  prop := VarArrayOf([propname]);
  Obj.GetInfoEx(prop, 0);
  try
    prop := Obj.GetEx(propname);
    n := VarArrayDimCount(prop);
    if n = 0 then
      Temp := Temp + VarToStr(prop)
    else if n = 1 then
      for I := VarArrayLowBound(prop, 1) to VarArrayHighBound(prop, 1) do
      begin
        subprop := prop[I];
        m := VarArrayDimCount(subprop);
        if m = 0 then
          Temp := Temp + VarToStr(subprop)
        else if m = 1 then
        begin
          for J := VarArrayLowBound(subprop, 1) to VarArrayHighBound(subprop, 1) do
            Temp := Temp + Chr(StrToInt(VarToStr(subprop[J])));
        end;
      end;
  except
    //on E: Exception do
    //  AddLog(logImport, 'error reading property ' + propname + ' : ' + E.Message);
  end;
  if (Temp <> '') and (Temp[1] <> '"') and (Temp[length(Temp)] <> '"') then
    Temp := AnsiQuotedStr(Temp, '"');
  Result := Temp;
end;
 
procedure TImportUsers.ImportUser(Obj: IAds);
var
  AccountName, FirstName, LastName, DisplayName, Descr, EMail, Attribs, aDomain, aPath: string;
  aUser: IADsUser;
 
  function GetProp(UserObj: IAdsUser; PropName: string): string;
  begin
    Result := '';
    try
      Result := UserObj.Get(propName);
    except
    end;
  end;
 
begin
  if not Terminated then
  try
    aUser := Obj as IADsUser;
    AccountName := GetProp(aUser, 'samAccountName');
    FirstName := GetProp(aUser, 'FirstName');
    LastName := GetProp(aUser, 'LastName');
    DisplayName := GetProp(aUser, 'FullName');
    Descr := GetProp(aUser, 'Description');
    Email := GetProp(aUser, 'mail');
    Attribs := ReadProperty(Obj);

Open in new window

0
 
QC20NAuthor Commented:
Please forgive me.
If you can categorise how many points should be given for a question I will be happy to give those points. I don't know how many points there should be for a question. It is not to be an dummy, but I just don't know.

br
QC20N
0
 
QC20NAuthor Commented:
I have created a user and I didn't login in.

And when I run the code it jumps to dt := 0; So I see your point. A user that never have login dosen't have a lastlogin but,  I know for a fact that some disabled users in our AD have logged in, but they fail.

Btw, how do I call:
procedure TImportUsers.ImportUser(Obj: IAds);

ImportUser(XXX);

0
 
QC20NAuthor Commented:
:) I now how call the procedure.

I have added this:

    alastLogin := GetProp(aUser, 'lastLogonTimestamp');

to the code you sent.

And call procedure

        if adusr.AccountDisabled {and (inttostr(MonthsBetween(date,adusr.lastlogin)) > '0')} then
        begin
          ImportUser(adusr);
          try
            dt := ADUsr.LastLogin;
          except
            dt := 0;
          end;


When I run the code it raise this exception:
Raised exception class EVariantTypeCastError
"Could not convert variant of type (Dispatch) into type (String)"

You know why?
0
 
Geert GruwezOracle dbaCommented:
i just posted some of my code ...
you will have your own code

but you would have to change the proc to suit your needs :


procedure TYourClass.ImportUser(aUser: IADsUser);
  ...
begin
  if not Terminated then
  try
    AccountName := GetProp(aUser, 'samAccountName');


0
 
QC20NAuthor Commented:
True. And most of the code I can use, but I have added an extra line to get the lastlogin.
0
 
QC20NAuthor Commented:
I will try again.

I have added this:

    alastLogin := GetProp(aUser, 'lastLogonTimestamp');

to the code you sent.

And call procedure

        if adusr.AccountDisabled then
        begin
  // The reason why both ImportUser and Try..Except is here. Is only to see wich of them is correct.
          ImportUser(adusr);
          try
            dt := ADUsr.LastLogin;
          except
            dt := 0;
          end;


When I run the code it raise this exception in the ImportUser proc:
Raised exception class EVariantTypeCastError
"Could not convert variant of type (Dispatch) into type (String)"

btw, how can I check the "logoncount"

If I do this:

    ADOQuery1.SQL.Text := 'SELECT LogonCount, samAccountName, distinguishedname, ADsPath,CN FROM '+ Quotedstr('LDAP://OU=USERS,' + edit2.Text) + ' WHERE objectClass='+ Quotedstr('user') + ' ORDER by CN';

and do this:

label8.Caption := ADOQuery1.FieldByName('LogonCount').AsString;

label8.caption is empty even though I know that it should be at least "0"

using AD Explorer from sysInternals to see the value.

0
 
Geert GruwezOracle dbaCommented:
some servers don't maintain counting logons
LogonAttemps yes, but that depends also on the policies set
0
 
QC20NAuthor Commented:
Ok. but I can see that the logoncount is in use on our users in the AD.
How can you collect logoncount data from AD? Is that an integer or a string?
0
 
QC20NAuthor Commented:
Well, the integer, string part I found out, but even I can see on one user that the logoncount is 140 I don't get any data from the logoncount.

What my goal is with all this is only to do delete the user when the user haven't been login and is disabled, so if I use your solution and dt is 0. The date will be 1899-30-12. But I really don't know if the user haven't been logged in for more than a month if the date i 1899-30-12.

try
dt := ADUsr.LastLogin;
except
dt := 0;
end;
0
 
Geert GruwezOracle dbaCommented:
this solution only provided a way of reading data which isn't there ...
it's up to you to combine both ...

screenshot.png
0
 
Geert GruwezOracle dbaCommented:
use logonCount
0
 
QC20NAuthor Commented:
My good expertfriend, if you see early in this thread, I ask how to use LogonCount, cause I tried this:

"
btw, how can I check the "logoncount"

If I do this:

ADOQuery1.SQL.Text := 'SELECT LogonCount, samAccountName, distinguishedname, ADsPath,CN FROM '+ Quotedstr('LDAP://OU=USERS,' + edit2.Text) + ' WHERE objectClass='+ Quotedstr('user') + ' ORDER by CN';

and do this:

label8.Caption := ADOQuery1.FieldByName('LogonCount').AsString;

label8.caption is empty even though I know that it should be at least "0"

using AD Explorer from sysInternals to see the value.

"
0
 
Geert GruwezOracle dbaCommented:
small letter l (first letter), caps letter C for count
logonCount
0
 
Geert GruwezOracle dbaCommented:
not LogonCount and not logoncount ...
0
 
Geert GruwezOracle dbaCommented:
the damndest thing about Active Directory = case sensitive !
0
 
QC20NAuthor Commented:
This is what my SQL is:
    ADOQuery1.SQL.Clear;
    ADOQuery1.SQL.Text := 'SELECT whenChanged, logonCount, samAccountName, distinguishedname, ADsPath,CN FROM '+ Quotedstr('LDAP://OU=USERS,' + edit2.Text) + ' WHERE objectClass='+ Quotedstr('user') + ' ORDER by CN';
    ADOQuery1.Open;

And I do this:

      ADsGetObject(ADOQuery1.FieldByName('AdsPath').AsString, IADsUser, adusr);
      if adusr <> nil then
      begin
        label8.Caption := ADOQuery1.FieldByName('logonCount').AsString;

but I don't get anything even that I can see by using AD Explorer from sysInternals.
0
 
QC20NAuthor Commented:
Jesus christ, if I use the code you sent previously it works. God damn. you are good.
0
 
QC20NAuthor Commented:
Ups, sorry. No such words. My mistake.
0
 
QC20NAuthor Commented:
Something I don't understand is:

why this dosen't work:
 alastLogin := GetProp(aUser, 'lastLogon');

but this do
dt := ADUsr.LastLogin;

on the same user.
0
 
Geert GruwezOracle dbaCommented:
you have a type libray imported ?
maybe the LastLogin function is a wrapper around a call to lastLogon
0
 
Geert GruwezOracle dbaCommented:
PS: God has nothing to do with it
0
 
QC20NAuthor Commented:
"you have a type libray imported ?"
Not sure what you mean
Just for the record. I still don't understand why
dt := ADUsr.LastLogin;

fails even when I know there is a lastLogin date.
Can see this by using AD Explorer from sysInternals
0
 
Geert GruwezOracle dbaCommented:
just for the record, Delphi + Active Directory
is one of the most difficult and cumbersome things i encountered up to now

just so you know what you are up against too :)
0
 
Geert GruwezOracle dbaCommented:
these are the units to talk to active directory
first is TLB (type library), which is where the function LastLogin will eventually lead

  ActiveDs_TLB in 'C:\Program Files\borland\Delphi7\Imports\ActiveDs_TLB.pas',
  adshlp in 'adshlp.pas',
0
 
QC20NAuthor Commented:
Yes, those are in my project. :)
0
 
QC20NAuthor Commented:
WHen I look in AD Explorer from sysInternals I can see that lastLogon has 2 values. Could that be the answer why I get an exception:

Raised exception class EVariantTypeCastError
"Could not convert variant of type (Dispatch) into type (String)"

when I do this:
alastLogin := GetProp(aUser, 'lastLogon');
0
 
Geert GruwezOracle dbaCommented:
have you tried debugging with the ReadProperty(adsuser, 'logonCount');

it may be an octetstring ...

thx for the link to the AD Explorer, we had problems we LDAP administrator and this is a real help.
now i don't have to bother the .Net guys anymore to get info
0
 
QC20NAuthor Commented:
I'm not sure how I can call ReadProperty with those parameters. As I see in your code previously ReadProperty is called like this:
Attribs := ReadProperty(Obj);

Btw, the lastLogon is in a integer8 format. Could that be the reason why it fails?
0
 
QC20NAuthor Commented:
And no problem with the link. Happy to help.
0

Featured Post

Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

  • 30
  • 22
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now