Autoit - Get the file size of all files in path - autoit

Im using a script to get all files and dirs in a specific and generates a .txt file of the result but i need to add the size of each file using the FileGetSize + ByteSuffix functions
#include <File.au3>
#include <WinAPIFiles.au3>
Global Enum Step *2 $GETFILES_NOT_DIRECTORY, $GETFILES_NOT_EXISTS ; GetFiles #error
Func G3tAllF1lesAndDirs($cpath, $txtname)
Local $sFileList = ''
GetFiles($sFileList, $cpath) ; This uses no global variable
$sFileList = StringReplace($sFileList, '|', #CRLF) ; Replace the pipe char for #CRLF
Local $handlepath = FileOpen(#DesktopDir & "\" & $txtname & ".txt",1)
; Write array to a file by passing the file name.
FileWrite($handlepath, $sFileList & #CRLF)
EndFunc ;==>Example
; By guinness on 2015/03/15. Idea by Belini and every AutoIt user who has done file searching.
Func GetFiles(ByRef $sFileList, $sFilePath)
Local Static $iCounter = 0
$sFilePath = _WinAPI_PathAddBackslash($sFilePath) ; Add backslash
If _WinAPI_PathIsDirectory($sFilePath) <> $FILE_ATTRIBUTE_DIRECTORY Then
Return SetError($GETFILES_NOT_DIRECTORY, 0, '')
EndIf
Local $hFileFind = FileFindFirstFile($sFilePath & '*')
If $hFileFind = -1 Then ; File not found
Return SetError($GETFILES_NOT_EXISTS, 0, '')
EndIf
Local $sFileName = ''
While True
$sFileName = FileFindNextFile($hFileFind)
If #error Then
ExitLoop
EndIf
If #extended Then ; Is directory.
$iCounter += 1 ; Used for recursion level
GetFiles($sFileList, $sFilePath & $sFileName)
$iCounter -= 1 ; Used for recursion level
Else
$sFileList &= $sFilePath & $sFileName & '|'
EndIf
WEnd
FileClose($hFileFind)
If $iCounter = 0 Then ; First recursion level, therefore strip pipe char
$sFileList = StringTrimRight($sFileList, StringLen('|'))
EndIf
EndFunc ;==>GetFiles
Func ByteSuffix($Bytes)
Local $Index = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
While $Bytes > 1023
$Index += 1
$Bytes /= 1024
WEnd
Return Round($Bytes) & $aArray[$Index]
EndFunc
G3tAllF1lesAndDirs(#ScriptDir, "files&folders")
Here is what i want in the .txt file by just modifying the script and using FileGetSize + ByteSuffix functions
C:\Users\G-PC\Documents\setup.exe [size of this]
C:\Users\G-PC\Documents\config.ini [size of this]
C:\Users\G-PC\Documents\image001.jpg [size of this]
C:\Users\G-PC\Documents\image002.jpg [size of this]
C:\Users\G-PC\Documents\image003.jpg [size of this]
C:\Users\G-PC\Documents\Videos\vid001.avi [size of this]
C:\Users\G-PC\Documents\Videos\vid002.avi [size of this]
C:\Users\G-PC\Documents\Videos\vid003.avi [size of this]
C:\Users\G-PC\Documents\Videos\vid004.avi [size of this]
C:\Users\G-PC\Documents\Videos\Comedy\vid001.avi [size of this]
C:\Users\G-PC\Documents\Videos\Comedy\vid002.avi [size of this]
C:\Users\G-PC\Documents\Videos\Comedy\vid003.avi [size of this]
The list is very long, i tried using using another script to overwrite the .txt file after it generates but it doesnt work/crash

What about this approach?
#include <File.au3>
#include <Array.au3>
Local $aArray = _FileListToArrayRec(#ScriptDir, "*", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_FASTSORT, $FLTAR_FULLPATH)
;~ _ArrayDisplay($aArray, "Sorted tree")
Local $size_A[UBound($aArray)][2]
For $i = 1 To UBound($aArray) -1
$size_A[$i][0] = $aArray[$i]
$size_A[$i][1] = '[' & ByteSuffix(FileGetSize($aArray[$i])) & ']'
Next
;~ _ArrayDisplay($size_A)
_FileWriteFromArray(#ScriptDir & '\size.txt', $size_A, 1, Default, ' ')
ShellExecute(#ScriptDir & '\size.txt')
Func ByteSuffix($Bytes)
Local $Index = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
While $Bytes > 1023
$Index += 1
$Bytes /= 1024
WEnd
Return Round($Bytes, 2) & $aArray[$Index]
EndFunc ;==>ByteSuffix
Here is another solution which should be easy to use and gives you the opportunity to append to existing files and to call the function multiple times.
#include <File.au3>
#include <Array.au3>
; Usage _outputFilesWithSize($STARTPATH, PATHOFOUTPUTFILE, MODE)
If _outputFilesWithSize(#ScriptDir, #ScriptDir & '\size.txt') <> 1 Then MsgBox(16, 'Error', 'Error') ;
If _outputFilesWithSize('c:\Users\xf01145\Documents\Books\', #ScriptDir & '\size.txt', 1) <> 1 Then MsgBox(16, 'Error', 'Error') ; 3rd Parameter 1 appends info to file
If _outputFilesWithSize('c:\Temp\', #ScriptDir & '\AnotherFile.txt') <> 1 Then MsgBox(16, 'Error', 'Error')
;~ ShellExecute(#ScriptDir & '\size.txt')
;===============================================================================
;
; Function Name: _outputFilesWithSize
; Description: Writes the full path and size of all files recursivly to a file
; Parameter(s): 1. startPath = starting directory
; 2. outputFile = path to the outputfile
; 3. Mode = Default = overwrite, 1 = append infos to file (e.g. multiple function calls)
; Requirement(s):
; Return Value(s): Error <> 1 = error writing the file
; Author(s): Xenobiologist
; Modified:
; Remarks:
;===============================================================================
;
Func _outputFilesWithSize($startPath, $outputFile = #ScriptDir & '\size.txt', $mode = Default)
Local $aArray = _FileListToArrayRec($startPath, "*", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_FASTSORT, $FLTAR_FULLPATH)
Local $size_A[UBound($aArray)][2]
For $i = 1 To UBound($aArray) - 1
$size_A[$i][0] = $aArray[$i]
$size_A[$i][1] = '[' & ByteSuffix(FileGetSize($aArray[$i])) & ']'
Next
If $mode = 1 Then $outputFile = FileOpen($outputFile, $FO_APPEND)
If _FileWriteFromArray($outputFile, $size_A, 1, Default, ' ') = 0 Then Return SetError(#error, 0, -1)
Return 1
EndFunc ;==>_outputFilesWithSize
Func ByteSuffix($Bytes)
Local $Index = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
While $Bytes > 1023
$Index += 1
$Bytes /= 1024
WEnd
Return Round($Bytes, 2) & $aArray[$Index]
EndFunc ;==>ByteSuffix

Related

Write to the end of each line -autoit

Instead of the MsgBox i want the script to read a .txt file and write to the end of each line the result of a variable, in my case is $FileSize
Here is the code for autoit script
#include <MsgBoxConstants.au3>
#include <File.au3>
Test()
Func Test()
For $i = 1 to _FileCountLines(#TempDir & "\myfiles.txt")
$mread = FileReadLine(#TempDir & "\myfiles.txt", $i)
Local $FileSize = FileGetSize($mread)
MsgBox($MB_SYSTEMMODAL, "", ByteSuffix($FileSize)) ;this is just a test
Next
EndFunc ;==>Example
Func ByteSuffix($Bytes)
Local $Index = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
While $Bytes > 1023
$Index += 1
$Bytes /= 1024
WEnd
Return Round($Bytes) & $aArray[$Index]
EndFunc
Here is the content of .txt file
C:\Users\G-PC\Documents\setup.exe
C:\Users\G-PC\Documents\config.ini
C:\Users\G-PC\Documents\image001.jpg
C:\Users\G-PC\Documents\image002.jpg
C:\Users\G-PC\Documents\image003.jpg
I want the following result
C:\Users\G-PC\Documents\setup.exe [SIZE FOR THIS]
C:\Users\G-PC\Documents\config.ini [SIZE FOR THIS]
C:\Users\G-PC\Documents\image001.jpg [SIZE FOR THIS]
C:\Users\G-PC\Documents\image002.jpg [SIZE FOR THIS]
C:\Users\G-PC\Documents\image003.jpg [SIZE FOR THIS]
There is a size limit for both string variables and arrays, so you have to process one line at a time when working with big files. This uses a temporary file for output instead of keeping all the information in memory. You can rename (overwriting the original file) the temporary file to the original file name at the end.
Func Test()
Local $InFile = FileOpen(#TempDir & "\myfiles.txt", $FO_READ)
Local $OutFile = FileOpen(#TempDir & "\myfiles.tmp", $FO_OVERWRITE)
While True
$File = FileReadLine($InFile)
If #error = -1 Then Return ; no more lines to process
FileWrite($OutFile, $File & " [" & ByteSuffix(FileGetSize($File)) & "]" & #CRLF)
WEnd
EndFunc ;==>Test
$sResult = Test()
ConsoleWrite('SIZE LIST' & #CRLF & $sResult)
Func Test()
Local $FileSize, $sReturn = ''
For $i = 1 to _FileCountLines(#TempDir & "\myfiles.txt")
$mread = FileReadLine(#TempDir & "\myfiles.txt", $i)
$FileSize = FileGetSize($mread)
; MsgBox($MB_SYSTEMMODAL, "", ByteSuffix($FileSize)) ;this is just a test
$sReturn &= $mread & " [" & ByteSuffix($FileSize) & "]" & #CRLF
Next
Return $sReturn
EndFunc ;==>Test
But a better way instead of reading the text file line by line is the using of _FileReadToArray. Than you can iterate through the array with text lines.
Using line number parameter for FileReadLine in a loop resets
the file pointer to the start and scans up to the line number which slows
down the loop as line count increases. Omit that parameter.
Use a file handle so you do not keep on opening and closing
the file with each line read in the loop.
#include <MsgBoxConstants.au3>
#include <File.au3>
$sResult = Test()
MsgBox(0, #ScriptName, $sResult)
Func Test()
Local $iFileSize, $hFile, $sFilePath, $sResult
; Open a file handle to read.
$hFile = FileOpen(#TempDir & "\myfiles.txt")
If $hFile = -1 Then
MsgBox(0x30, #ScriptName, 'Unable to open file to read.')
Exit 1
EndIf
While 1
; Read and let AutoIt handle line count.
$sFilePath = FileReadLine($hFile)
If #error Then ExitLoop
$iFileSize = FileGetSize($sFilePath)
If #error Then ContinueLoop
$sResult &= StringFormat('%s %s\r\n', $sFilePath, ByteSuffix($iFileSize))
WEnd
FileClose($hFile)
Return $sResult
EndFunc
Func ByteSuffix($Bytes)
Local $Index = 0
Local $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
While $Bytes > 1023
$Index += 1
$Bytes /= 1024
WEnd
Return Round($Bytes) & $aArray[$Index]
EndFunc

autoit - randomize text replacement with stringreplace function

So I am in need of replacing string text with autoit using stringreplace but I need to randomize the output.
An example of what I need is
Stringreplace($string, "and", {also|as well})
My ultimate goal is to randomly replace the text with the following options based on the word "and" with also or as well
I wrote this a long time ago.
It will convert this
My name is {John|Peter|Mark}! {Regards|Cheers|Get lost}!
to something like this
My name is John! Cheers!
It works with line breaks also.
Func SpintaxToTXT($TextWithSpintax)
Dim $MSGMSG
Dim $lines
$lines = StringSplit($TextWithSpintax, #LF)
For $z = 1 To $lines[0]
If $z > 1 Then $MSGMSG &= #LF
$d = StringSplit($lines[$z], "{}")
For $i = 1 To $d[0]
$MSGSplit = StringSplit($d[$i], "|")
If #error Then
$MSGMSG &= $MSGSplit[1]
ContinueLoop
EndIf
$MSGMSG &= $MSGSplit[Random(1, $MSGSplit[0], 1)]
Next
Next
Return $MSGMSG
EndFunc ;==>SpintaxToTXT
Try this:
Global $options_A = StringSplit('also,as well,bla,blubb,that,this', ',', 2)
For $i = 0 To 20
ConsoleWrite($options_A[Random(0, UBound($options_A) - 1, 1)] & #CRLF)
Next
Here's one I made earlier. Works exactly the same as StringReplace except instead of taking a replacement string, it takes a function that returns the replacement string. The using Xenobiologist's array method, you get the desired result.
Local $sTest = "The cat and the dog and the rat."
ConsoleWrite(_StringReplaceCallback($sTest, "and", _MyCallback) & #LF)
Func _MyCallback($s)
Local Static $aOptions = StringSplit("also|as well", "|")
Return $aOptions[Random(1, $aOptions[0], 1)]
EndFunc ;==>_MyCallback
Func _StringReplaceCallback($sString, $sFind, $funcReplace, $iOccurence = 0, $iCaseSense = 0)
Local $sRet = ""
Local $iDir = 1
Local $iPos = 1
If $iOccurence < 0 Then
$iDir = -1
$iPos = StringLen($sString)
EndIf
If $iOccurence = 0 Then $iOccurence = $iDir * StringLen($sString)
While 1
$i = StringInStr($sString, $sFind, $iCaseSense, $iDir, $iPos)
If $iDir > 0 Then
If Not $i Or Not $iOccurence Then
$sRet &= StringMid($sString, $iPos)
ExitLoop
EndIf
$sRet &= StringMid($sString, $iPos, $i - $iPos) & $funcReplace($sFind)
$iPos = $i + StringLen($sFind)
Else
If Not $i Or Not $iOccurence Then
$sRet = StringMid($sString, 1, $iPos) & $sRet
ExitLoop
EndIf
$sRet = $funcReplace($sFind) & StringMid($sString, $i + StringLen($sFind), $iPos - $i - StringLen($sFind) + 1) & $sRet
$iPos = $i - 1
EndIf
If $iOccurence <> 0 Then $iOccurence -= $iDir
WEnd
Return $sRet
EndFunc ;==>_StringReplaceCallback
It's probably worth noting that there is actually a simpler solution for this, provided the replacement strings don't ever include "and" (or else you'll have an infinite loop), that just replaces one instance at a time:
Local $sString = "The cat and the dog and the rat."
Local $aOptions = StringSplit("also|as well", "|")
Do
$sString = StringReplace($sString, "and", $aOptions[Random(1, $aOptions[0], 1)], 1)
Until Not #extended
ConsoleWrite($sString & #LF)

My script is returning 0 even though the CSV source is not empty

I'm in the process of making a small program to tell me on the fly how many employees are currently punched in via AvayaCMS Reporting.
Right now, the way that it is setup is that I have an Avaya script to take a quick report and export it in a CSV that is used by my Autoit script.
In terms of debugging it, I feel like I missing something and need another set of eyes.
launching the Staffing.au3 triggers the CSV script I'm using against the report. Even having the exact same data my message box still reports "0"
#include <Array.au3>
#include <CSV.au3>
$i_AgentCount = AvayaData(#ScriptDir & '\Report.csv', #ScriptDir & '\Name List.csv')
MsgBox(0x40, "", $i_AgentCount)
Func AvayaData($s_ReportSource, $s_NameList)
$av_LiveData = _ParseCSV($s_ReportSource)
If #error Then Return -1
$av_NameList = _ParseCSV($s_NameList)
If #error Then Return -1
Local $i_AgentCount = 0
For $i_RowCnt = 1 To (UBound($av_LiveData, 1) - 1) Step +1
For $i_AgtLst = 1 To (UBound($av_NameList) - 1) Step +1
If StringStripWS($av_LiveData[$i_RowCnt][1], 3) = StringStripWS($av_NameList[$i_AgtLst][0], 3) Then
$i_AgentCount += 1
EndIf
Next
Next
;Return the Agent Count
Return $i_AgentCount
EndFunc
Name List.csv
Agent Name
"Doe, Jane"
"Doe, John"
Report.csv
,Agent Name,Login ID,Extn,AUX Reason,State,Split/Skill,Time,VDN Name
5,"Doe, John",5930001,1000001,7,AUXOUT,999,51:32:00,
2,"Doe, Jane",5930002,1000002,7,AUXOUT,999,52:32:00,
Tested! It works well with the files supplied (copied them and names them as in your script)
please note the followings
_ParseCsv() has been rewritten add parameters like $Dchar as delimeter etc. (see script)
Note how I locate the names in the file (you can easily add feature to extend search but it won't be becessary)
msgboxes are for explanatory purposes; comment them out if not need them anymore
array.au3 is not needed
and the code:
$i_AgentCount = AvayaData(#ScriptDir & '\Report.csv', #ScriptDir & '\Name List.csv')
MsgBox(0x40, "", $i_AgentCount)
Func AvayaData($s_ReportSource, $s_NameList)
$av_LiveData = _ParseCSV($s_ReportSource,",","oops...",True)
If #error Then Return -1
$av_NameList = _ParseCSV($s_NameList,",","oops: 2",True)
If #error Then Return -1
MsgBox(0,default,"$av_NameList (Number of lines) -> " & $av_NameList[0][0])
MsgBox(0,default,"$av_NameList (Number of data in each line) -> " & $av_NameList[0][1])
MsgBox(0,default,"$av_LiveData (Number of lines) -> " & $av_LiveData[0][0])
MsgBox(0,default,"$av_LiveData (Number of data in each line) -> " & $av_LiveData[0][1])
Local $i_AgentCount = 0
for $i = 1 to $av_NameList[0][0] Step 1
For $j= 1 To $av_LiveData[0][0] Step 1
;we can have names from $av_NameList as well from $av_LiveData but in Live Data 2nd abd 3rd values are the names
MsgBox(0,default,$av_NameList[$i][1] & $av_NameList[$i][2] & " <------------> " & $av_LiveData[$j][2] & $av_LiveData[$j][3])
;~ let's compare them if matched with any of the liveData lines
If StringCompare($av_NameList[$i][1] & $av_NameList[$i][2],$av_LiveData[$j][2] & $av_LiveData[$j][3]) == 0 Then
$i_AgentCount += 1
MsgBox(0,default,"Match found: counter: " & $i_AgentCount)
EndIf
Next
Next
;Return the Agent Count
Return $i_AgentCount
EndFunc
Func _ParseCSV($f,$Dchar,$error,$skip)
Local $array[500][500]
Local $line = ""
$i = 0
$file = FileOpen($f,0)
If $file = -1 Then
MsgBox(0, "Error", $error)
Return False
EndIf
;skip 1st line (since it is the header)
If $skip Then $line = FileReadLine($file)
While 1
$i = $i + 1
Local $line = FileReadLine($file)
If #error = -1 Then ExitLoop
;skip 1st line
$row_array = StringSplit($line,$Dchar)
If $i == 1 Then $row_size = UBound($row_array)
If $row_size <> UBound($row_array) Then MsgBox(0, "Error", "Row: " & $i & " has different size ")
$row_size = UBound($row_array)
$array = _arrayAdd_2d($array,$i,$row_array,$row_size)
WEnd
FileClose($file)
$array[0][0] = $i-1 ;stores number of lines
$array[0][1] = $row_size -1 ; stores number of data in a row (data corresponding to index 0 is the number of data in a row actually that's way the -1)
Return $array
EndFunc
Func _arrayAdd_2d($array,$inwhich,$row_array,$row_size)
For $i=1 To $row_size -1 Step 1
$array[$inwhich][$i] = $row_array[$i]
Next
Return $array
EndFunc
#region ;************ Includes ************
#include "csv.au3"
#include <Array.au3>
#endregion ;************ Includes ************
$i_AgentCount = AvayaData(#ScriptDir & '\Report.csv', #ScriptDir & '\Name List.csv')
MsgBox(0x40, "", $i_AgentCount)
Func AvayaData($s_ReportSource, $s_NameList)
Local $i_AgentCount = 0
Local $s = FileRead($s_NameList)
Local $loggedInNames = FileRead($s_ReportSource)
$av_NameList = _ParseCSV($s_NameList)
If #error Then Return -1
;~ _ArrayDisplay($av_NameList)
ConsoleWrite($s & #CRLF)
For $i = 1 To UBound($av_NameList) - 1
ConsoleWrite($i & " " & $av_NameList[$i][0] & #CRLF)
If StringInStr($s, $av_NameList[$i][0]) <> 0 Then
$i_AgentCount += 1
EndIf
Next
Return $i_AgentCount
EndFunc ;==>AvayaData

Search string in file and print next 5th line in file using autoit

I have a text file contains nearly 20 lines wanted to search a string in file and print next 5th line in file using autoit, any one can help me to solve this
#include <File.au3>
#include <array.au3>
$file = #ScriptDir & "\file.txt"
$search = "str"
If FileExists($file) Then
$contents = FileRead($file)
If #error Then
MsgBox(0, 'File Error', $file & ' could not be read.')
Else
For $i = 1 To $count
If StringInStr($contents, $search) Then
MsgBox(0, 'Positive', $file & ' does contain the text "' & $search & '"')
Else
MsgBox(0, 'Negative', $file & ' does NOT contain the text "' & $search & '"')
EndIf
Next
EndIf
EndIf
This reads the text file until the search string is found and then writes the next 5 lines to STDOUT:
#include <File.au3>
#include <Array.au3>
Global $file = #ScriptDir & "\file.txt", $search = "str"
Global $iLine = 0, $sLine = ''
Global $hFile = FileOpen($file)
If $hFile = -1 Then
MsgBox(0,'ERROR','Unable to open file for reading.')
Exit 1
EndIf
; find the line that has the search string
While 1
$iLine += 1
$sLine = FileReadLine($hFile)
If #error = -1 Then ExitLoop
; $search found in the line, now write the next 5 lines to STDOUT
If StringInStr($sLine, $search)And Not $iValid Then
For $i = $iLine+1 To $iLine+5
ConsoleWrite($i & ':' & FileReadLine($hFile, $i) & #CRLF)
Next
ExitLoop
EndIf
WEnd
FileClose($hFile)
Edit
Due to Matt's argument here's a 2nd version of the loop that doesn't use the "line" parameter for FileReadLine.
#include <File.au3>
#include <Array.au3>
Global $file = #ScriptDir & "\file.txt", $search = "str"
Global $iLine = 0, $sLine = '', $iValid = 0
Global $hFile = FileOpen($file)
If $hFile = -1 Then
MsgBox(0,'ERROR','Unable to open file for reading.')
Exit 1
EndIf
; find the line that has the search string
While 1
$iLine += 1
$sLine = FileReadLine($hFile)
If #error = -1 Then ExitLoop
; test the line for the $search string until the flag $iValid is set
If StringInStr($sLine, $search) And Not $iValid Then
$iValid = 1
ContinueLoop
EndIf
If $iValid Then
$iValid += 1
ConsoleWrite($iLine & ':' & $sLine & #CRLF)
If $iValid > 5 Then ExitLoop
EndIf
WEnd
FileClose($hFile)
You won't note much difference between those two versions of the script, unless you are reading a file with 10k+ lines and the lines you are looking for are in the last quarter of that file but it surely is a good idea to prevent possible performance issues.

MsiEnumRelatedProducts returns ERROR_INVALID_PARAMETER with iProductIndex > 0

I'm trying to write an AutoIt script that uninstalls all MSI packages with a specific Upgrade Code. This is my code so far:
$i = 0
Do
$buffer = DllStructCreate("wchar[39]")
$ret = DllCall("msi.dll", "UINT", "MsiEnumRelatedProductsW", _
"wstr", "{a1b6bfda-45b6-43cc-88de-d9d29dcafdca}", _ ; lpUpgradeCode
"dword", 0, _ ; dwReserved
"dword", $i, _ ; iProductIndex
"ptr", DllStructGetPtr($buffer)) ; lpProductBuf
$i = $i + 1
MsgBox(0, "", $ret[0] & " " & DllStructGetData($buffer, 1))
Until($ret[0] <> 0)
This works flawlessly to determine the Product Code for the first installed product, but it returns 87 (ERROR_INVALID_PARAMETER) as soon as iProductIndex is incremented to 1. Usually this error is returned when the input GUID is malformed, but if that would be the case, it shouldn't work with iProductIndex = 0 either...
What I expected from this code (when 2 packages with the same Upgrade Code are installed) is:
Print "0 <first Product Code>"
Print "0 <second Product Code>"
Print "259" (ERROR_NO_MORE_ITEMS)
What it currently does:
Print "0 <first Product Code>"
Print "87" (ERROR_INVALID_PARAMETER)
Any ideas?
(If you want to test this code on your own computer, you will need to have two MSI packages with the same UpgradeCode installed. Here are my WiX test packages: http://pastie.org/3022676 )
OK, I've found a simple workaround: I just remove every product I can find with iProductIndex = 0 in a loop.
Func GetProduct($UpgradeCode)
$buffer = DllStructCreate("wchar[39]")
$ret = DllCall("msi.dll", "UINT", "MsiEnumRelatedProductsW", _
"wstr", $UpgradeCode, _ ; lpUpgradeCode
"dword", 0, _ ; dwReserved
"dword", 0, _ ; iProductIndex
"ptr", DllStructGetPtr($buffer)) ; lpProductBuf
Return DllStructGetData($buffer, 1)
EndFunc
$Last = ""
$Product = ""
Do
$Last = $Product
$Product = GetProduct("{a1b6bfda-45b6-43cc-88de-d9d29dcafdca}")
If $Product = "" Then Exit
$Ret = RunWait("msiexec /qn /x " & $Product)
ConsoleWrite($Ret & " " & $Product & #CRLF)
If $Product = $Last Then Exit 1
Until($product = "")
This doesn't work because using DllCall() the DLL is not kept open. The function MsiEnumRelatedProducts propably has internal state that is required for the enumeration and is only initialized when the index is zero. When the DLL is closed, this state is lost.
To fix this, call DllOpen() before the loop. Keep the DLL open while the loop is running and pass the DLL handle instead of its filename to DllCall(). Close the DLL using DllClose() when the loop has been finished.
Here is a function that returns an array of ProductCodes for the given UpgradeCode. It returns Null in case the function didn't found any products.
Func GetRelatedProducts( $UpgradeCode )
Local $result[ 1 ] ; Can't declare empty array :/
Local $dll = DllOpen( "msi.dll" )
If #error Then Return SetError( 1, #error, Null )
Local $buffer = DllStructCreate( "wchar[39]" )
Local $index = 0
Local $success = False
Do
Local $ret = DllCall( $dll, "UINT", "MsiEnumRelatedProductsW", _
"wstr", $UpgradeCode, _ ; lpUpgradeCode
"dword", 0, _ ; dwReserved
"dword", $index, _ ; iProductIndex
"ptr", DllStructGetPtr($buffer)) ; lpProductBuf
If #error Then
DllClose( $dll )
Return SetError( 1, #error, Null )
EndIf
$success = $ret[ 0 ] = 0 ; $ret[ 0 ] contains the DLL function's return value
If( $success ) Then
Local $productCode = DllStructGetData( $buffer, 1 )
Redim $result[ $index + 1 ]
$result[ $index ] = $productCode
$index += 1
EndIf
Until( Not $Success )
DllClose( $dll )
if( $index ) Then
Return $result
Else
Return Null
EndIf
EndFunc
Usage:
Local $productCodes = GetRelatedProducts( "{insert-upgradecode-here}" )
If( IsArray( $productCodes ) ) Then
MsgBox( 0, "Success!", "Found products:" & #CRLF & _ArrayToString( $productCodes, #CRLF ) )
EndIf

Resources