Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

A little help with Win32::API?

by flamey (Scribe)
on Apr 25, 2010 at 01:20 UTC ( [id://836719]=perlquestion: print w/replies, xml ) Need Help??

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

I was hoping the following code would give me the target of an adnertised shortcut in Windows. Well, in the code below, to MS Word 2007 installed on my PC.

use 5.10.0; use strict; use warnings; use Win32::API; # UINT MsiGetShortcutTarget( # __in LPCTSTR szShortcutTarget, # __out LPTSTR szProductCode, # __out LPTSTR szFeatureId, # __out LPTSTR szComponentCode # ); my $MsiGetShortcutTarget = Win32::API->new( 'msi.dll', 'UINT MsiGetShortcutTarget(LPCTSTR targetFile, LPTSTR p +roductCode, LPTSTR featureID, LPTSTR componentCode)', ); my( $file, $product, $feature, $component ) = ( 'Microsoft Office Word 2007.lnk', "\0" x 39, "\0" x 39, "\0" x 39 ); my $retCode = $MsiGetShortcutTarget->Call( $file, $product, $feature, +$component ); say $retCode; $product =~ s/\0.*$//; $component =~ s/\0.*$//; say "$product|\n$feature|\n$component|"; # INSTALLSTATE MsiGetComponentPath( # __in LPCTSTR szProduct, # __in LPCTSTR szComponent, # __out LPTSTR lpPathBuf, # __inout DWORD *pcchBuf # ); my $MsiGetComponentPath = Win32::API->new( 'msi.dll', 'MsiGetComponentPath', 'PPPP', 'I' ); my( $path, $pathLength ) = ( "\0" x 260, "\0" x 8 ); my $installState = $MsiGetComponentPath->Call( $product, $component, $ +path, $pathLength ); say $installState; $pathLength = unpack( 'L', $pathLength ); say $pathLength; say "{$path}";

After a call to MsiGetShortcutTarget() I get return code 0 (success), and correct values for the product feature and component.

After a call to MsiGetComponentPath(), I get the correct $pathLength ( on my PC its 54 ->

C:\Program Files\Microsoft Office\Office12\WINWORD.EXE
).. but the $path is unchanged - it's still 260 blank chars.

What am I doing wrong?

Replies are listed 'Best First'.
Re: A little help with Win32::API? (solution)
by ikegami (Patriarch) on Apr 25, 2010 at 02:51 UTC

    "On input, [*pcchBuf] is the full size of the buffer, including a space for a terminating null character.", so you're telling Windows $path has can accommodate 0 bytes. (A very useful trick is to call such functions twice, once to get the size needed, and once with the proper buffer size.)

    my( $path, $pathLength ) = ( "\0" x 260, "\0" x 8 );

    should be

    use constant MAX_PATH => 260; my $path = "\0" x MAX_PATH; my $pathLength = pack('L', MAX_PATH);

    By the way, 8 bytes was longer than needed.

      thank you so much for explanation! and of course this fixes everything
Re: A little help with Win32::API? (cleanup)
by ikegami (Patriarch) on Apr 25, 2010 at 03:05 UTC

    Below is how I would have implemented the solution. In short, I'd use constants instead of magical values, and I'd hide C-ish bits in a sub that provides an interface better suited to Perl.

    use v5.10.0; use strict; use warnings; use Win32::API qw( ); use constant MAX_PATH => 260; use constant MAX_FEATURE_CHARS => 38; use constant { INSTALLSTATE_NOTUSED => -7, # component disabled INSTALLSTATE_BADCONFIG => -6, # configuration data corrupt INSTALLSTATE_INCOMPLETE => -5, # installation suspended or in p +rogress INSTALLSTATE_SOURCEABSENT => -4, # run from source, source is una +vailable INSTALLSTATE_MOREDATA => -3, # return buffer overflow INSTALLSTATE_INVALIDARG => -2, # invalid function argument INSTALLSTATE_UNKNOWN => -1, # unrecognized product or featur +e INSTALLSTATE_BROKEN => 0, # broken INSTALLSTATE_ADVERTISED => 1, # advertised feature INSTALLSTATE_REMOVED => 1, # component being removed (actio +n state, not settable) INSTALLSTATE_ABSENT => 2, # uninstalled (or action state a +bsent but clients remain) INSTALLSTATE_LOCAL => 3, # installed on local drive INSTALLSTATE_SOURCE => 4, # run from source, CD or net INSTALLSTATE_DEFAULT => 5, # use default, local or source }; my %INSTALLSTATE_DESC = ( INSTALLSTATE_NOTUSED() => 'The component being requested is d +isabled on the computer', INSTALLSTATE_BADCONFIG() => '[configuration data corrupt]', INSTALLSTATE_INCOMPLETE() => '[installation suspended or in prog +ress]', INSTALLSTATE_SOURCEABSENT() => 'The component source is inaccessib +le', INSTALLSTATE_MOREDATA() => '[return buffer overflow]', INSTALLSTATE_INVALIDARG() => 'One of the function parameters is +invalid', INSTALLSTATE_UNKNOWN() => 'The product code or component ID i +s unknown', INSTALLSTATE_BROKEN() => '[broken]', INSTALLSTATE_ADVERTISED() => '[advertised feature]', INSTALLSTATE_REMOVED() => '[component being removed (action s +tate, not settable)]', INSTALLSTATE_ABSENT() => 'The component is not installed', INSTALLSTATE_LOCAL() => 'The component is installed locally +', INSTALLSTATE_SOURCE() => 'The component is installed to run +from source', INSTALLSTATE_DEFAULT() => '[use default, local or source]', ); { # UINT MsiGetShortcutTarget( # __in LPCTSTR szShortcutTarget, # __out LPTSTR szProductCode, # __out LPTSTR szFeatureId, # __out LPTSTR szComponentCode # ); my $MsiGetShortcutTarget = Win32::API->new( 'msi.dll', 'MsiGetShortcutTarget', 'PPPP', 'N'); sub MsiGetShortcutTarget { my ($szShortcutTarget) = @_; $szShortcutTarget .= "\0"; my $szProductCode = "\0" x 39; my $szFeatureId = "\0" x (MAX_FEATURE_CHARS+1); my $szComponentCode = "\0" x 39; return () if $^E = $MsiGetShortcutTarget->Call( $szShortcutTarget, $szProductCode, $szFeatureId, $szComponentCode, ); s/\0.*//s for $szProductCode, $szFeatureId, $szComponentCode; return ( $szProductCode, $szFeatureId, $szComponentCode ); } } { # INSTALLSTATE MsiGetComponentPath( # __in LPCTSTR szProduct, # __in LPCTSTR szComponent, # __out LPTSTR lpPathBuf, # __inout DWORD *pcchBuf # ); my $MsiGetComponentPath = Win32::API->new( 'msi.dll', 'MsiGetComponentPath', 'PPPP', 'I'); sub MsiGetComponentPath { my ($szProduct, $szComponent) = @_; $szProduct .= "\0"; $szComponent .= "\0"; my $lpPathBuf = "\0" x MAX_PATH; my $pcchBuf = pack('L', MAX_PATH); my $rv = $MsiGetComponentPath->Call( $szProduct, $szComponent, $lpPathBuf, $pcchBuf, ); return ( $rv, substr($lpPathBuf, 0, unpack('L', $pcchBuf)) ); } } { my $shortcut = 'Microsoft Office Word 2007.lnk'; my ($product, $feature, $component) = MsiGetShortcutTarget($shortc +ut) or die("MsiGetShortcutTarget: $^E\n"); say "product: $product"; say "feature: $feature"; say "component: $component"; my ($install_state, $path) = MsiGetComponentPath($product, $component); say "install state: $INSTALLSTATE_DESC{$install_state}"; say "install path: $path"; }

    Untested (since I don't know of what advertised shortcuts exist on my machine).

    Update: Changed +CONSTANT to CONSTANT() for hash keys.

      Thanks for the code - it's beautiful :) And it works.

      Well, almost. The %INSTALLSTATE_DESC in your code gets initialized as { "INSTALLSTATE_NOTUSED" => "description", ... }, however MsiGetShortcutTarget() return integer. I guess the +INSTALLSTATE_NOTUSED hash init doesn't do what you intended on my machine (AS Perl 5.10.0, MSWin32, osvers=5.00, archname=MSWin32-x86-multi-thread) (I've never seen this technique)

      Other improvements can be made to this code I noticed:

      • szProduct and szComponent are UUIDs in brackets, so we can define constant for them as well to be 38 chars
      • If I understand this correctly, MsiGetComponentPath() returns how successful the execution went (any of Win32/MSI errors; this list, i guess); we supposed to call MsiGetComponentPath() only if it returns success (0).
        Usually you get either ERROR_SUCCESS (0) or ERROR_FUNCTION_FAILED (1627). But on Vista I also get 1603 when I try this with c#, though the same code woks fine on XP. Furthermore, on Vista Perl code works just fine where my C# code fails. I probably don't know what I'm doing, but now I know for sure return code isn't limited to ERROR_SUCCESS or ERROR_FUNCTION_FAILED :)

      But I do get the path, and that's what I wanted to just quickly do.

        I guess the +INSTALLSTATE_NOTUSED hash init doesn't do what you intended

        Fixed.

        szProduct and szComponent are UUIDs in brackets, so we can define constant for them as well to be 38 chars

        True. I just used the number because the documentation stated a number and not a constant.

        If I understand this correctly, MsiGetComponentPath() returns how successful the execution went

        I think you mean MsiGetShortcutTarget. I made the Perl version return an empty list on error. The reason for the error is available via $^E (0+$^E gives the code, ''.$^E gives the message).

        we supposed to call MsiGetComponentPath() only if it returns success (0).

        I do that. I don't know why you say this is an improvement to be made.

        But on Vista I also get 1603 when I try this with c#

        Not a problem. I figured the errors might not be limited to 1627. By using $^E, it's properly handled.

      > Untested (since I don't know of what advertised shortcuts exist on my machine).
      Acrobat Reader 8 or higher gets installed with one, MS Office 2007. If it was installed with .msi file, there's a chance they used advertised shortcuts.
Re: A little help with Win32::API? (alternative?)
by ikegami (Patriarch) on Apr 25, 2010 at 02:11 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://836719]
Approved by hawtin
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (2)
As of 2024-04-25 20:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found