billie_t has asked for the wisdom of the Perl Monks concerning the following question:

A script is provided in Roth's Win32 Perl Scripting's book to get the last true logon time for a Windows Domain user by querying all the domain controllers to get the REAL time a user last logged on or off.

The script was written for an NT domain, and the part that enumerates the PDC/BDCs in the domain (using WIN32::NetAdmin) does not work for a Win2K environment. So, I tried to hack the script by giving a fixed array of domain controllers (not the optimum, but sufficient for me), with the result the script doesn't work as expected.

Instead of polling the domain controllers and returning the LAST logon time over all the controllers, the script is only returning the data from the last server in the array (although they all appear to be being polled). I'm reluctant to give the entire script, but I'm appending the bits that do most of the work:
if( "" ne $Config{domain} ) { # $Config{machine} = ""; # Win32::NetAdmin::GetDomainController( '', # $Config{domain}, # $Config{machine} ); # Win32::NetAdmin::GetServers( $Config{machine}, # $Config{domain}, # SV_TYPE_DOMAIN_CTRL, # \@MachineList ); @MachineList = qw(SERVER1 SERVER2 SERVER3); }

That code above shows howthe MachineList array was being populated orginally, and the fixed list that I have substituted.

#... stuff to populate %accountslist, $account ... foreach my $Machine ( @MachineList ) { ( $Machine = "\\\\$Machine" ) =~ s/^\\+/\\\\/; print "Querying $Machine\n"; foreach my $Account ( sort( keys( %AccountList ) ) ) { my %Attrib; if( Win32::AdminMisc::UserGetMiscAttributes( $Machine, $Account, \%Attrib ) ) { my $Data = $Result{$Account} = {}; $Data->{fullname} = $Attrib{USER_FULL_NAME}; if( $Data->{lastlogon}->{value} < $Attrib{USER_LAST_LOGON} ) { $Data->{lastlogon}->{value} = $Attrib{USER_LAST_LOGON}; $Data->{lastlogon}->{machine} = $Machine; } if( $Data->{lastlogoff}->{value} < $Attrib{USER_LAST_LOGOFF} ) { $Data->{lastlogoff}->{value} = $Attrib{USER_LAST_LOGOFF}; $Data->{lastlogoff}->{machine} = $Machine; } $Data->{badpwcount} += $Attrib{USER_BAD_PW_COUNT}; $Data->{logons} += $Attrib{USER_NUM_LOGONS}; } } } foreach my $Account ( sort( keys( %Result ) ) ) { print "$Account ($Result{$Account}->{fullname}):\n"; print Report( "Last logon", $Result{$Account}->{lastlogon} ), "\n"; print Report( "Last logoff", $Result{$Account}->{lastlogoff} ), "\n" +; print "\tTotal number of bad password attempts: "; print "$Result{$Account}->{badpwcount}\n"; print "\tTotal number of logons: $Result{$Account}->{logons}\n"; print "\n"; } sub Report { my( $Field, $Data ) = @_; my $Date = scalar localtime($Data->{value} ); my $Location = "( $Data->{machine} )"; $Date = "Not available" if( 0 == $Data->{value} ); $Location = "" if( 0 == $Data->{value} ); return( "\t$Field: $Date $Location" ); }
So, when running the full script, all I get is the results returned for SERVER3. If anyone can shed some light on this, I'd be grateful.

Replies are listed 'Best First'.
Re: Problem with ascertaining last NT logon - Roth script
by Roger (Parson) on Feb 17, 2004 at 04:20 UTC
    Looks to me that your script actually polled the results for all the servers, but because you are not populating your hash %Results "correctly", the previous results are overwritten by most recent results. What you need to do is to introduce another hash level, so that your %Results looks like this:
    $Results{$Machine}{$Account}{$attributes}

    And then you iterate through your machines first, then for each machine, you iterate through your accounts, and so on...

    If you want to order by the accounts, you build your %Results like this:
    $Results{$Account}{$Machine}{$attributes}

    ie, swap the account and machine, and you iterate through account, then machine, and so on...

      Thanks for the explanation, Roger. I'm afraid that being a complete noob when it comes to Perl, I can find it quite difficult to visualise what is going on within a hash, particularly if elements are being referenced by other variables.

      I can see that my %Result is fairly much only storing {$account} and {%attrib}. And I understand the idea that you need another level (for {%machine}?) in the %Result to store what went on before. But to be frank, thinking in more than 2 hash levels is beyond me at present (some things I can visualise by RFTM, others need to be explained - I'm not a programmer).

      Would it be possible for you (or someone else) to give me a snippet of code to show what you mean? Apologies for the "obviousness" of the question... :-(

      Also, any links to simple discussions of hashes etc would be helpful - my books are good, but coming at it from a different perspective sometimes helps.

        Ok, here comes the code... Give this a try. See if this gives you what you expect:
        foreach my $Machine ( @MachineList ) { ( $Machine = "\\\\$Machine" ) =~ s/^\\+/\\\\/; print "Querying $Machine\n"; foreach my $Account ( sort keys %AccountList ) { my %Attrib; if( Win32::AdminMisc::UserGetMiscAttributes( $Machine, $Account, \%Attrib ) ) { my $Data = $Result{$Machine}{$Account} = {}; $Data->{fullname} = $Attrib{USER_FULL_NAME}; if( $Data->{lastlogon}->{value} < $Attrib{USER_LAST_LOGON} ) { $Data->{lastlogon}->{value} = $Attrib{USER_LAST_LOGON}; $Data->{lastlogon}->{machine} = $Machine; } if( $Data->{lastlogoff}->{value} < $Attrib{USER_LAST_LOGOFF} ) { $Data->{lastlogoff}->{value} = $Attrib{USER_LAST_LOGOFF}; $Data->{lastlogoff}->{machine} = $Machine; } $Data->{badpwcount} += $Attrib{USER_BAD_PW_COUNT}; $Data->{logons} += $Attrib{USER_NUM_LOGONS}; } } } foreach my $Machine ( sort keys %Result ) { my $MachineInfo = $Results{$Machine}; foreach my $Account ( sort keys %{$MachineInfo} ) { print "$Account ($MachineInfo->{$Account}{fullname}):\n"; print Report( "Last logon", $MachineInfo->{$Account}{lastlogon +} ), "\n"; print Report( "Last logoff", $MachineInfo->{$Account}{lastlogo +ff} ), "\n"; print "\tTotal number of bad password attempts: "; print "$MachineInfo->{$Account}{badpwcount}\n"; print "\tTotal number of logons: $MachineInfo->{$Account}{logo +ns}\n"; print "\n"; } } sub Report { my( $Field, $Data ) = @_; my $Date = scalar localtime($Data->{value} ); my $Location = "( $Data->{machine} )"; $Date = "Not available" if( 0 == $Data->{value} ); $Location = "" if( 0 == $Data->{value} ); return( "\t$Field: $Date $Location" ); }