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

Greetings, brothers,

Update: took someone's suggestion about readmore tags....

Long story short: I have a need to test a Windows machine's ability to locate a Windows domain controller, and my homebrew algorithm came up short. I spelunked around CPAN for awhile and concluded that there were no modules already built to do this. So I decided to turn to the experts at Microsoft to see how they did it. They use an API provided by the NetLogon service with a C prototype that looks like this:


DWORD DsGetDcName( __in LPCTSTR ComputerName, __in LPCTSTR DomainName, __in GUID *DomainGuid, __in LPCTSTR SiteName, __in ULONG Flags, __out PDOMAIN_CONTROLLER_INFO *DomainControllerInfo );

As you can see, the goods are returned via the PDOMAIN_CONTROLLER_INFO parameter, a pointer to a DomainControllerInfo struct.

That struct looks like this:


typedef struct _DOMAIN_CONTROLLER_INFO { LPTSTR DomainControllerName; LPTSTR DomainControllerAddress; ULONG DomainControllerAddressType; GUID DomainGuid; LPTSTR DomainName; LPTSTR DnsForestName; ULONG Flags; LPTSTR DcSiteName; LPTSTR ClientSiteName; } DOMAIN_CONTROLLER_INFO, *PDOMAIN_CONTROLLER_INFO;

There is also a GUID struct specified in the prototype. According to the MSDN docs, it can be null, but things turned nasty when I didn't define it. Here's a look at its definition:


typedef struct _GUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; }GUID;

Hmm, I thought, this is my opportunity to get more familiar with Win32::API. Goody. I'm no C programmer, but I'll try to fake it long enough to get *one simple function call working*. Riiiight....
So after a few hours of reading docs and coding, I came up with something.

An abbreviated version of that looks like this:

use Win32::API; use strict; use Error qw(:try); use Data::Dumper; # Define result constants use constant ERROR_INVALID_DOMAINNAME => 1212; # The form +at of the specified DomainName is invalid. use constant ERROR_INVALID_FLAGS => 1004; # The Flag +s parameter contains conflicting or superfluous flags. use constant ERROR_NOT_ENOUGH_MEMORY => 8; # A memory + allocation failure occurred. use constant ERROR_NO_SUCH_DOMAIN => 1355; # No domai +n controller is available for the specified domain or the domain does + not exist. use constant ERROR_SUCCESS => 0; # No worri +es! # Define structs used as parameters typedef Win32::API::Struct GUID => qw { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; }; typedef Win32::API::Struct DOMAIN_CONTROLLER_INFO => qw { LPTSTR DomainControllerName; LPTSTR DomainControllerAddress; ULONG DomainControllerAddressType; GUID DomainGuid; LPTSTR DomainName; LPTSTR DnsForestName; ULONG Flags; LPTSTR DcSiteName; LPTSTR ClientSiteName; }; # Create instances of these structs my $domainguid = Win32::API::Struct->new('GUID'); my $dci = Win32::API::Struct->new('DOMAIN_CONTROLLER_INFO'); #Import our function my $DsGetDcName = Win32::API->new('Netapi32', 'DsGetDcName', 'PPSPNS', + 'N') or die Win32::FormatMessage(Win32::GetLastError); if (not defined $DsGetDcName) {die "Couldn't get DsGetDcName! $!\n";} try { # Call the imported function... my $dc = $DsGetDcName->Call('mymachine', 'mydomain', $domainguid, + 'myADsite', 0x00020000, $dci); print "Return value: [$dc]\n"; if ($dc == ERROR_INVALID_DOMAINNAME) {print "ERROR_INVALID_DOMAIN +NAME\n"} if ($dc == ERROR_INVALID_FLAGS) {print "ERROR_INVALID_FLAGS\n"} if ($dc == ERROR_NOT_ENOUGH_MEMORY) {print "ERROR_NOT_ENOUGH_MEMO +RY\n"} if ($dc == ERROR_NO_SUCH_DOMAIN) {print "ERROR_NO_SUCH_DOMAIN\n"} if ($dc == ERROR_SUCCESS) {print "ERROR_SUCCESS\n"} } catch Error with { my $err = shift; print "Error: $err\n"; }; print Dumper($dci);

Essentially, DsGetDcName returns a value of 0 when it works, and a non-zero value (potential values are defined in the constants) when something breaks. I *am* getting a 0 value (and I get the constant values when I break things deliberately), so I have every reason to believe that the call itself is actually working.

The problem is I cannot decipher the output no matter how I try. Data::Dumper outputs something that looks like this:


'DcSiteName' => '', 'DnsForestName' => '', 'Flags' => 0, 'DomainControllerName' => 'áµ ', 'DomainControllerAddress' => '', 'buffer' => 'pµ ╘g ( &#9 +560;g (╘g ( ╘g (╘g (', '__typedef__' => 'DOMAIN_CONTROLLER_INFO', 'ClientSiteName' => '', 'DomainGuid' => bless( { '__typedef__' => 'GUID', 'Data1' => 0, 'Data3' => 0, 'typedef' => [ [ 'Data1', 'L', 'DWORD' ], [ 'Data2', 'S', 'WORD' ], [ 'Data3', 'S', 'WORD' ], [ 'Data4', 'C*8', 'BYTE' ] ], 'Data4' => '', 'Data2' => 0 }, 'Win32::API::Struct' ), 'buffer_recipients' => [ $VAR1, $VAR1, $VAR1, $VAR1->{'DomainGuid'}, $VAR1->{'DomainGuid'}, $VAR1->{'DomainGuid'}, $VAR1->{'DomainGuid'}, $VAR1, $VAR1, $VAR1, $VAR1, $VAR1 ], 'typedef' => [ [ 'DomainControllerName', 'p', 'LPTSTR' ], [ 'DomainControllerAddress', 'p', 'LPTSTR' ], [ 'DomainControllerAddressType', 'L', 'ULONG' ], [ 'DomainGuid', '>', 'GUID' ], [ 'DomainName', 'p', 'LPTSTR' ], [ 'DnsForestName', 'p', 'LPTSTR' ], [ 'Flags', 'L', 'ULONG' ], [ 'DcSiteName', 'p', 'LPTSTR' ], [ 'ClientSiteName', 'p', 'LPTSTR' ] ], 'DomainName' => '', 'DomainControllerAddressType' => 0 }, 'Win32::API::Struct' );

See the funky chars in the DomainControllerName element? That's what I'm talkin' about.
A careful re-re-re-read of the Win32::API documentation revealed:

ABSTRACT ^ With this module you can import and call arbitrary functions from Win3 +2's Dynamic Link Libraries (DLL), without having to write an XS exten +sion. Note, however, that this module can't do everything. In fact, p +arameters input and output is limited to simpler cases.

At this point, what I need is closure! :-) Have I run headfirst into the brick wall of "this is one of those not-simple cases for which the module will not work?!"
I eagerly await your insight. Thank you!

--Geoff

Replies are listed 'Best First'.
Re: Win32::API: deciphering a returned pointer to a struct (DCI** != DCI*)
by ikegami (Patriarch) on Dec 23, 2009 at 03:39 UTC

    The DomainControllerInfo arg is described as:

    Pointer to a PDOMAIN_CONTROLLER_INFO value that receives a pointer to a DOMAIN_CONTROLLER_INFO structure that contains data about the domain controller selected. This structure is allocated by DsGetDcName. The caller must free the structure using the NetApiBufferFree function when it is no longer required.

    And if you look at the function declaration,

    PDOMAIN_CONTROLLER_INFO *DomainControllerInfo

    It's suppose to be a pointer to a pointer that it will populate, not a pointer to a struct that it will populate.

    Completely untested (and you need to fill a couple of question marks):

    use strict; use warnings; use Carp qw( croak ); use Win32::API qw( ); use Win32::API::Struct qw( ); use constant DS_IS_DNS_NAME => 0x00020000; typedef Win32::API::Struct GUID => qw { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; }; sub _link { my $o = Win32::API->new( @_ ); die "Can't link to DsGetDcName in dll Netapi32: $^E\n" if !$o; return $o; } { my $DsGetDcName = _link( 'Netapi32', 'DsGetDcName', 'PPSPNP', 'N' ); my $NetApiBufferFree = _link( 'Netapi32', 'NetApiBufferFree', 'N', 'N' ); my $GUID_FORMAT = 'L S S a8'; my $SIZE_OF_GUID = 4+2+2+ 8; my $DCI_FORMAT = "p p L a$SIZE_OF_GUID p p L p p"; my $SIZE_OF_DCI = 4+4+4+ $SIZE_OF_GUID+4+4+4+4+4; sub DsGetDcName { my $pk_ptr_to_pk_dci = pack('L', 0); if (!(my $rv = $DsGetDcName->Call( @_, $pk_ptr_to_pk_dci ))) { # It uses the same codes as GetLastError $^E = $rv; croak("$^E"); } my $ptr_to_pk_dci = unpack('L', $pk_ptr_to_pk_dci); my $pk_dci = unpack("P$SIZE_OF_DCI", $pk_ptr_to_pk_dci); # If only Win32::API::Struct provided a public # interface that takes a packed struct. my ($dcn, $dca, $dcat, $pk_guid, $dn, $dfn, $flags, $sn, $csn) = unpack($DCI_FORMAT, $pk_dci); my @guid = unpack($GUID_FORMAT, $pk_guid); my $guid = Win32::API::Struct->new('GUID'); @{$guid}{qw( Data1 Data2 Data3 Data4 )} = @guid; if (!(my $rv = $NetApiBufferFree->Call( $ptr_to_pk_dci ))) { # It uses the same codes as GetLastError $^E = $rv; # Don't use carp since it's likely a bug in this sub. warn("Warning: Error from NetApiBufferFree: $^E"); } return { DomainControllerName => $dcn, DomainControllerAddress => $dca, DomainControllerAddressType => $dcat, DomainGuid => \@guid, DomainName => $dn, DnsForestName => $dfn, Flags => $flags, DcSiteName => $sn, ClientSiteName => $csn, }; } } my $domainguid = Win32::API::Struct->new('GUID'); my $dci = DsGetDcName( 'mymachine', 'mydomain', $domainguid, 'myADsite', DS_IS_DNS_NAME, ); use Data::Dumper qw( Dumper ); print Dumper $dci;
Re: Win32::API: deciphering a returned pointer to a struct
by Util (Priest) on Aug 21, 2009 at 18:57 UTC

    1. Always turn on double quotes before using Data::Dumper on data with possible unprintable data;
      otherwise, you cannot tell what your data really is:
      local $Data::Dumper::Useqq = 1;
    2. Ignore the keys typedef and buffer_recipients in the Dumper output. This will leave you with just the expected fields in DOMAIN_CONTROLLER_INFO, and an extra buffer field which contains the raw bytes of the structure.
    3. What happens if you declare the DsGetDcName API using the `prototype` style, instead of the older `parameter list` style you are using? Does that resolve the problem, or at least change the behavior? i.e. change:
      my $DsGetDcName = Win32::API->new('Netapi32', 'DsGetDcName', 'PPSPNS', + 'N') or die Win32::FormatMessage(Win32::GetLastError);
      to:
      my $DsGetDcName_declare = <<'END_OF_DECLARE'; DWORD DsGetDcName( LPCTSTR ComputerName, LPCTSTR DomainName, LPGUID DomainGuid, LPCTSTR SiteName, ULONG Flags, LPDOMAIN_CONTROLLER_INFO DomainControllerInfo ); END_OF_DECLARE my $DsGetDcName = Win32::API->new( Netapi32 => $DsGetDcName_declare ) or die Win32::FormatMessage(Win32::GetLastError);