bluebearr Posted August 19, 2006 Posted August 19, 2006 I'm trying to write a script to enumerate all of the MSIs installed on a system. I think that I can probably do this with WMI or COM, but I'd like to be able to do it with DllCall because I have another scripting language that I may want to use the function in that doesn't support either of them, and for the learning exercise (ok, I'm a masochist). The weird thing is, the function _GetMSIInstalledApps() below works if the index $i = 0, but not if it is anything else. After the first value, it returns ERROR_INVALID_PARAMETER. I've included the URL and some of the text from the MSDN entry concerning this function. Any help is appreciated; I'd really like to not have to struggle so much with DLL calls. expandcollapse popup#Include <String.au3> #include <Array.au3> global $ret $aApps = _GetMSIInstalledApps() If StringLeft($aApps[0][0], 5) == "ERROR" Then MsgBox(0, "_GetMSIInstalledApps Error", "Error : " & $aApps[0][0] & " (" & $aApps[0][1] & ")") Exit EndIf $fileH = FileOpen(@ScriptDir & "\InstalledApps.txt", 2) For $j = 1 To $aApps[0][0] FileWriteLine($fileH, $aApps[$j][0] & @TAB & $aApps[$j][1]) Next ; ============== ; FUNCTIONS ; ============== Func _GetMSIInstalledApps() ; Enumerate MSIs Const $ERROR_SUCCESS = 0 Dim $i = 0, $sProducts[1][2] $ret = DllCall("msi.dll", "int", "MsiEnumProductsA", "int", $i, "str", '') _ArrayDisplay($ret, "Debug: DllCall Return") While $ret[0] = $ERROR_SUCCESS MsgBox(0, 'Debug _GetMSIInstalledApps', 'Current Code = ' & $ret[2]) $i = $i ReDim $sProducts[$i + 1][2] $sProducts[$i][0] = RegRead('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' & $ret[2], 'DisplayName') $sProducts[$i][1] = $ret[2] $ret = DllCall("msi.dll", "int", "MsiEnumProductsA", "int", $i, "str", '') _ArrayDisplay($ret, "Debug: DllCall Return") WEnd $retC = _GetReturnCode($ret[0]) If $retC <> "ERROR_NO_MORE_ITEMS" Then ;MsgBox(0, "_GetMSIInstalledApps Error", "Error : " & $retC & " (" & $ret[0] & ")" & @LF & "$i = " & $i) Dim $errs[1][2] $errs[0][0] = $retC $errs[0][1] = $ret[0] Return $errs Else $sProducts[0][0] = $i Return $sProducts EndIf EndFunc Func _GetReturnCode($iCode) ; Translate the error code into error text Dim $sMsg = '' $sErrTexts = StringSplit("ERROR_BAD_CONFIGURATION,ERROR_INVALID_PARAMETER," _ & "ERROR_NO_MORE_ITEMS,ERROR_NOT_ENOUGH_MEMORY,ERROR_SUCCESS", ",") $iErrCodes = StringSplit("1610,87,259,8,0", ",") For $i = 1 To $iErrCodes[0] If $iCode = $iErrCodes[$i] Then $sMsg = $sErrTexts[$i] EndIf Next If $sMsg = '' Then $sMsg = "UNKNOWN_ERROR" Return $sMsg EndFunc ;~ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/msienumproducts.asp ;~ MsiEnumProducts ;~ The MsiEnumProducts function enumerates through all the products currently advertised or installed. ;~ Both per-user and per-machine installations and advertisements are enumerated. ;~ UINT MsiEnumProducts( ;~ DWORD iProductIndex, ;~ LPTSTR lpProductBuf ;~ ); ;~ Parameters ;~ iProductIndex ;~ [in] Specifies the index of the product to retrieve. This parameter should be zero for the first call ;~ to the MsiEnumProducts function and then incremented for subsequent calls. Because products are not ordered, ;~ any new product has an arbitrary index. This means that the function can return products in any order. ;~ lpProductBuf ;~ [out] Pointer to a buffer that receives the product code. This buffer must be 39 characters long. ;~ The first 38 characters are for the GUID, and the last character is for the terminating null character. ;~ Return Values ;~ Value Meaning ;~ ERROR_BAD_CONFIGURATION The configuration data is corrupt. ;~ ERROR_INVALID_PARAMETER An invalid parameter was passed to the function. ;~ ERROR_NO_MORE_ITEMS There are no products to return. ;~ ERROR_NOT_ENOUGH_MEMORY The system does not have enough memory to complete the operation. Available with Windows Server 2003 family. ;~ ERROR_SUCCESS A value was enumerated. BlueBearrOddly enough, this is what I do for fun.
GaryFrost Posted August 19, 2006 Posted August 19, 2006 When making multiple calls to MsiEnumProducts to enumerate all of the products, each call should be made from the same thread. SciTE for AutoItDirections for Submitting Standard UDFs Don't argue with an idiot; people watching may not be able to tell the difference.
GaryFrost Posted August 19, 2006 Posted August 19, 2006 (edited) Edit: changed for loop for While loop expandcollapse popup#Include <String.au3> #include <Array.au3> Global $Debugit = 0 Global $ERROR_BAD_CONFIGURATION = 1610 Global $ERROR_INVALID_PARAMETER = 87 Global $ERROR_NO_MORE_ITEMS = 259 Global $ERROR_NOT_ENOUGH_MEMORY = 8 Global $ERROR_SUCCESS = 0 $dll = DllOpen("msi.dll") Global $ret, $aApps[1][2], $x = 0 While 1 _GetMSIInstalledApps($dll, $x, $aApps) If @error Then ExitLoop $x += 1 WEnd Switch @error Case $ERROR_BAD_CONFIGURATION ConsoleWrite("ERROR_BAD_CONFIGURATION" & @LF) Exit Case $ERROR_INVALID_PARAMETER ConsoleWrite("$ERROR_INVALID_PARAMETER" & @LF) Exit Case $ERROR_NO_MORE_ITEMS ConsoleWrite("$ERROR_NO_MORE_ITEMS" & @LF) Case $ERROR_NOT_ENOUGH_MEMORY ConsoleWrite("$ERROR_NOT_ENOUGH_MEMORY" & @LF) Exit EndSwitch $fileH = FileOpen(@ScriptDir & "\InstalledApps.txt", 2) For $j = 1 To $aApps[0][0] FileWriteLine($fileH, $aApps[$j][0] & @TAB & $aApps[$j][1]) Next ; ============== ; FUNCTIONS ; ============== Func _GetMSIInstalledApps(ByRef $dll, ByRef $i_index, ByRef $aApps) ; Enumerate MSIs $ret = DllCall($dll, "int", "MsiEnumProducts", "int", $i_index, "str", "") If $ret[0] <> 0 Then Return SetError($ret[0], $ret[0], $ret[0]) ReDim $aApps[UBound($aApps) + 1][2] $aApps[0][0] = UBound($aApps) - 1 $aApps[UBound($aApps) - 1][0] = RegRead('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' & $ret[2], 'DisplayName') $aApps[UBound($aApps) - 1][1] = $ret[2] If $Debugit Then _DebugPrint("Debug: DllCall Return:" & @LF & "--> " & $ret[0] & @LF & "--> " & $ret[1] & @LF & "--> " & $ret[2]) EndFunc ;==>_GetMSIInstalledApps Func OnAutoItExit() DllClose($dll) EndFunc ;==>OnAutoItExit Func _DebugPrint($s_text) $s_text = StringReplace($s_text, @LF, @LF & "-->") ConsoleWrite("!===========================================================" & @LF & _ "!===========================================================" & @LF & _ "-->" & $s_text & @LF & _ "!===========================================================" & @LF) EndFunc ;==>_DebugPrint Edited August 19, 2006 by gafrost SciTE for AutoItDirections for Submitting Standard UDFs Don't argue with an idiot; people watching may not be able to tell the difference.
bluebearr Posted August 19, 2006 Author Posted August 19, 2006 Thank you, very nice! BlueBearrOddly enough, this is what I do for fun.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now