in reply to A little help with Win32::API?

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.

Replies are listed 'Best First'.
Re^2: A little help with Win32::API? (cleanup)
by flamey (Scribe) on Apr 25, 2010 at 08:54 UTC

    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.

        thanks! I've learned a few new things in Perl with this thread...
Re^2: A little help with Win32::API? (cleanup)
by flamey (Scribe) on Apr 25, 2010 at 09:00 UTC
    > 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.