|
|
|||||||
I work at a school district and our users need to have network printers loaded onto computers based on the location of the computer. Teachers can move around the building (and even to other buildings) and need to have access to the closest printer. I posted this thread awhile ago to get an initial feeler for how to go about it. So the deployment was this week and I got the bugs ironed out. Below is the final script. EDITED 5/1/2013 to include some extra options for the INI and moving the global declaration out of the function as suggested below. First the UDF's I'm using: Code: ;FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=118766 function GetDefaultPrinter() $GetDefaultPrinter = join(split(readvalue("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows","Device"),',',1),'') endfunction ;FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=202790#Post202790 Function IniArray($_fSrcFile, OPTIONAL $_aDataWrite) Dim $_ ; temp var Dim $_Fp ; file pointer Dim $_I, $_J ; index pointers Dim $_Sect ; Section Name Dim $_aDat ; Data pair Dim $_aSect[1,0], $_aData[1, 0] ; Section & Data Arrays ; Obtain a File Handle for Read or Write operations ; ============================================================ $_Fp = FreeFileHandle ; locate an available file handle If Not $_Fp Exit 1 ; none available - exit EndIf ; WRITE: verify that we have properly formatted data ; ============================================================ If VarType($_aDataWrite) > 0 ; Write the array to the INI file and exit If VarType($_aDataWrite) < 8192 ; data but not an array - exit! Exit 87 EndIf Del $_fSrcFile ; delete any pre-existing file $_ = Open($_Fp, $_fSrcFile, 5) ; open the file for write/create If @ERROR Exit @ERROR EndIf ; exit if error opening ; Write the section data. If no data exists in the section, do not write anything! For $_I = 0 to UBound($_aDataWrite, 2) $_Sect = $_aDataWrite[0, $_I] ; Section name to write $_aData = $_aDataWrite[1, $_I] ; Data array If UBound($_aData, 2) > -1 ; create Sect and write data if data is present $_ = '[' + $_Sect + ']' + @CRLF ; section name For $_J = 0 to UBound($_aData, 2) ; key/data pairs If $_aData[1, $_J] ; only write keys that have non-null data $_ = $_ + $_aData[0, $_J] + '=' + $_aData[1, $_J] + @CRLF EndIf Next $_ = WriteLine($_Fp, $_) ; write the output line If @ERROR Exit @ERROR EndIf ; exit if error writing EndIf Next $_ = Close($_Fp) ; close the file ReDim $_aData[1, 0] ; clear array ; exit 0 ; do not exit here to force a re-read of the freshly written data EndIf ; READ: Load the ini file into an array ; ============================================================ $_I = -1 $_J = -1 ; Initialize index pointers $_ = Open($_Fp, $_fSrcFile, 2) ; open the file for read If @ERROR Exit @ERROR EndIf ; exit if error opening Do ; loop through the file contents $_ = Trim(ReadLine($_Fp)) ; remove leading/trailing spaces If Left($_, 1) = '[' ; found a section If $_I >= 0 ; process prior section data, if any $_aSect[1, $_I] = $_aData ReDim $_aData[1, 0] $_J = -1 EndIf $_I = $_I + 1 ; increment the section index ReDim Preserve $_aSect[1, $_I] $_aSect[0, $_I] = Split(SubStr($_, 2), ']')[0] Else If Not Left($_, 1) = ';' And Not Left($_, 1) = '#' And Len($_) > 2 $_aDat = Split($_, '=') ; break into array $_J = $_J + 1 ; increment the data index ReDim Preserve $_aData[1, $_J] $_aData[0, $_J] = $_aDat[0] ; Store the data $_aData[1, $_J] = $_aDat[1] ; Store the data EndIf EndIf Until @ERROR ; done with input data $_ = Close($_Fp) ; close the file ; process the last/only section If $_I >= 0 $_aSect[1, $_I] = $_aData EndIf $IniArray = $_aSect ; return the array Exit 0 ; exit success EndFunction Function ReadIniArray($_aIniFile, OPTIONAL $_Section, OPTIONAL $_Key) ; exit immediately if the data format is invalid If VarType($_aIniFile) < 8192 ; data but not an array - exit! Exit 87 EndIf Dim $_aSectIdx[UBound($_aIniFile, 2)] ; Section Index Array Dim $_I, $_J ; Index pointers Dim $_aData ; Array of Key/Data pairs ; Create a section index array For $_I = 0 to UBound($_aIniFile, 2) $_aSectIdx[$_I] = $_aIniFile[0, $_I] Next ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] If Not $_Section $ReadIniArray = Join($_aSectIdx, Chr(10)) Exit 0 EndIf ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) If $_I < 0 Exit 2 EndIf ; section not found - Exit $_aData = $_aIniFile[1, $_I] ; Extract the key/value array ; Create a Key index for the requested section Dim $_aKeyIdx[UBound($_aData, 2)] For $_J = 0 to UBound($_aData, 2) $_aKeyIdx[$_J] = $_aData[0, $_J] Next ; If the Key is null, return a delimited string of Keys [same as ReadProfileString(file, section)] If Not $_Key $ReadIniArray = Join($_aKeyIdx, Chr(10)) Exit 0 EndIf ; Search the index for a Key $_J = aScan($_aKeyIdx, $_Key) If $_J < 0 Exit 2 EndIf ; Key not found $ReadIniArray = $_aData[1, $_J] Exit 0 EndFunction ;Modified FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=203126#Post203126 function INISections($array) $read = readiniarray($array) if LEN($read) > 0 $INISections=split($read,chr(10)) else $INISections = "" endif endfunction function INIKeys($array,$section) $read = readiniarray($array,$section) if LEN($read) > 0 $INIKeys=split($read,chr(10)) else $INIKeys = "" endif endfunction ;FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=84020 function mid($midstring,$midstart,optional $midend) if $midend<0 $midend=abs($midend) else if $midend>len($midstring)-abs($Midstart) or $midend="" $midend=0 endif endif select case $midstart<0 $midstart=abs($midstart) If $midend=0 $mid=left(right($midstring,len($midstring)),(len($midstring)-$midstart)+1) else $mid=left(right($midstring,($midstart+$midend)-1),$midend) endif case $midstart>0 if $midend=0 $mid= right(left($midstring,len($midstring)),(len($midstring)-$midstart)+1) else $mid=right(left($midstring,($midstart+$midend)-1),$midend) endif endselect endfunction And here is my script: Code: $debug = 0 IF NOT InStr(UCase(@PRODUCTTYPE), 'SERVER') AND NOT InStr(UCase(@PRODUCTTYPE), 'DOMAIN') ;DONT TOUCH SERVERS $userid = Ucase(@USERID) $workstation = Ucase(@WKSTA) $domain = Ucase(@DOMAIN) ;IF $debug = 1 $workstation = "aj324-3" ENDIF $school=Left($workstation, 2) $netprint_ini = "" SELECT case $school = "AJ" OR $domain = "JUNIOR" $netprint_ini = "script_netprint_JUNIOR.ini" case $school = "AS" OR $domain = "SENIOR" $netprint_ini = "script_netprint_SENIOR.ini" case $school = "CB" OR $domain = "COPPER" case $school = "HI" OR $domain = "HIGHLAND" case $school = "MK" OR $domain = "MCKINLEY" case $school = "OV" OR $domain = "OVERLOOK" case $school = "RO" OR $domain = "ROSLYN" case $school = "RW" OR $school = "RY" OR $school = "RE" OR $domain = "RYDAL" case $school = "WH" OR $domain = "WILLOW_HILL" case $school = "AA" OR $domain = "ADMIN" ;must be last $netprint_ini = "script_netprint_ADMIN.ini" ENDSELECT IF NOT $netprint_ini = "" SELECT case EXIST(@ScriptDir + "\" + $netprint_ini) IF $debug = 1 ? @Time + ": NP: INI found in script dir" ENDIF $netprint_ini = @ScriptDir + "\" + $netprint_ini case EXIST(@LSERVER + "\NETLOGON\" + $netprint_ini) IF $debug = 1 ? @Time + ": NP: INI found at logon server" ENDIF $netprint_ini = @LSERVER + "\NETLOGON\" + $netprint_ini case EXIST(@LDRIVE + $netprint_ini) IF $debug = 1 ? @Time + ": NP: INI found at logon drive" ENDIF $netprint_ini = @LDRIVE + $netprint_ini ENDSELECT IF $debug = 1 ? @Time + ": NP: ini: " + $netprint_ini ENDIF $network_printers = IniArray($netprint_ini) IF NOT @ERROR = 0 IF $debug = 1 ? "Error opening file: " + @ERROR + ": " + @SERROR ENDIF ELSE $current_default_printer = GetDefaultPrinter GLOBAL $prefix GLOBAL $roomnumber GLOBAL $beforedash GLOBAL $afterdash GLOBAL $pcnumber $prefix = "" $roomnumber = "" $beforedash = "" $afterdash = "" $pcnumber = "" $ret = DecipherComputerName($workstation) IF $debug = 1 ? @Time + ": NP: prefix: '" + $prefix + "'" ? @Time + ": NP: room: '" + $roomnumber + "'" ? @Time + ": NP: beforedash: '" + $beforedash + "'" ? @Time + ": NP: afterdash: '" + $afterdash + "'" ? @Time + ": NP: pcnum: '" + $pcnumber + "'" ENDIF GLOBAL $building_printers GLOBAL $new_default_printer $building_printers = 1 $new_default_printer = "" $printers_added = 0 ;[COMPUTERNAME\USERNAME] IF $printers_added = 0 $section = $workstation + '\' + $userid $printers_added = ProcessSection($network_printers, $section) ENDIF ;[COMPUTERNAME-*\USERNAME] IF $printers_added = 0 IF NOT $beforedash = "" $section = $beforedash ELSE $section = $prefix + CSTR($roomnumber) ENDIF $section = $section + '-*\' + $userid $printers_added = ProcessSection($network_printers, $section) ENDIF ;[COMPUTERNAME] IF $printers_added = 0 $section = $workstation $printers_added = ProcessSection($network_printers, $section) ENDIF ;[COMPUTERNAME-*] IF $printers_added = 0 IF NOT $beforedash = "" $section = $beforedash ELSE $section = $prefix + CSTR($roomnumber) ENDIF $section = $section + '-*' $printers_added = ProcessSection($network_printers, $section) ENDIF IF $printers_added = 0 IF $debug = 1 ? @Time + ": NP: Iterating sections..." ENDIF $sections = INISections($network_printers) For Each $section in $sections IF $printers_added = 0 ; [PREFIX_LOWRM#-LOWPC# through PREFIX_HIGHRM#-HIGHPC#] ;OR [PREFIX-LOWPC# through PREFIX-HIGHPC#] $t = INSTR($section, " through ") IF $t > 0 AND NOT $pcnumber = "" AND $pcnumber = $afterdash AND (IsNumeric($roomnumber) = 1 OR $roomnumber = "") IF $debug = 1 ? @Time + ": NP: Processing section '" + $section + "' ..." ENDIF $section_prefix = "" $room_low = "" $room_high = "" $pc_low = "" $pc_high = "" $dash = "" For $i = 1 to $t $char = MID($section, $i, 1) SELECT CASE $char = "*" $pc_low = "0" CASE $char = "-" $dash = $char CASE $char = " " $i = 99999 CASE IsNumeric($char) = 1 IF $dash = "" $room_low = $room_low + $char ELSE $pc_low = $pc_low + $char ENDIF CASE 1 IF $room_low = "" $section_prefix = $section_prefix + $char ENDIF ENDSELECT Next IF $debug = 1 ? @Time + ": NP: 1st prefix: " + $section_prefix ENDIF IF $section_prefix = $prefix AND NOT $pc_low = "" $dash = "" $section_prefix = "" For $i = ($t+LEN(" through ")) to LEN($section) $char = MID($section, $i, 1) SELECT CASE $char = "*" $pc_high = "32768" CASE $char = "-" $dash = $char CASE $char = " " $i = 99999 CASE IsNumeric($char) = 1 IF $dash = "" $room_high = $room_high + $char ELSE $pc_high = $pc_high + $char ENDIF CASE 1 IF $room_high = "" $section_prefix = $section_prefix + $char ENDIF ENDSELECT Next IF $debug = 1 ? @Time + ": NP: 2nd prefix: " + $section_prefix ENDIF IF $section_prefix = $prefix AND NOT $pc_high = "" $room_low = VAL($room_low) $room_high = VAL($room_high) $pc_low = VAL($pc_low) $pc_high = VAL($pc_high) IF $debug = 1 ? @Time + ": NP: room low: " + $room_low ? @Time + ": NP: room high: " + $room_high ? @Time + ": NP: pc low: " + $pc_low ? @Time + ": NP: pc high: " + $pc_high ENDIF IF VAL($pcnumber) >= $pc_low AND VAL($pcnumber) <= $pc_high IF ((VAL($roomnumber) >= $room_low AND VAL($roomnumber) <= $room_high) OR ($roomnumer = "" AND $room_low = 0 AND $room_high = 0)) IF $debug = 1 ? @Time + ": NP: section matched." ENDIF $printers_added = ProcessSection($network_printers, $section) ELSE IF $debug = 1 ? @Time + ": NP: Machine out of room number range." ENDIF ENDIF ELSE IF $debug = 1 ? @Time + ": NP: Machine out of section's PC number range." ENDIF ENDIF ELSE IF $debug = 1 ? @Time + ": NP: Section prefix doesn't match OR pc_high not found." ENDIF ENDIF ELSE IF $debug = 1 ? @Time + ": NP: Section prefix doesn't match OR pc_low not found." ENDIF ENDIF ELSE ;[BEFOREDASH-PC1,PC2,PC3] $c = INSTR($section, ",") $d = INSTR($section, "-") IF $d > 0 AND $c > $d IF $debug = 1 ? @Time + ": NP: Processing section '" + $section + "' ..." ENDIF $section_beforedash = MID($section, 1, $d-1) IF $debug = 1 ? @Time + ": NP: section beforedash: '" + $section_beforedash + "'" ENDIF IF $section_beforedash = $beforedash $postfixes = SPLIT(MID($section, $d+1), ",") For Each $section_afterdash in $postfixes IF $printers_added = 0 IF $debug = 1 ? @Time + ": NP: section afterdash: '" + $section_afterdash + "'" ENDIF IF $section_afterdash = $afterdash IF $debug = 1 ? @Time + ": NP: section matched." ENDIF $printers_added = ProcessSection($network_printers, $section) ENDIF ENDIF Next ELSE IF $debug = 1 ? @Time + ": NP: Section before dash doesn't match." ENDIF ENDIF ENDIF ENDIF ENDIF Next ENDIF IF $printers_added = 1 IF $building_printers = 1 IF ContainsNumbers($userid) < 2 AND INGROUP("Students") = 0 ;NON-STUDENTS COLOR w/n ? " Adding Printers, please wait..." IF NOT $debug = 1 COLOR n/n ENDIF $ret = ProcessSection($network_printers, "BUILDING_PRINTERS", 1) ENDIF ENDIF IF NOT $new_default_printer = "" $ret = SETDEFAULTPRINTER($new_default_printer) IF $debug = 1 ? @Time + ": NP: " + "SETDEFAULT( $new_default_printer ) -- RETURN: " + $ret ENDIF ELSE IF NOT $current_default_printer = "" $ret = SETDEFAULTPRINTER($current_default_printer) IF $debug = 1 ? @Time + ": NP: " + "SETDEFAULT( $current_default_printer ) -- RETURN: " + $ret ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF function ProcessSection($iniarray, $section, OPTIONAL $ignoredefault) $ProcessSection = 0 IF $debug = 1 ? @Time + ": NP: Processing section '" + $section + "' ..." ENDIF $keylist = INIKeys($iniarray, $section) IF UBOUND($keylist) >= 0 IF $debug = 1 ? @Time + ": NP: Section Found..." ENDIF $ProcessSection = 1 For $i = 0 to UBOUND($keylist) $value = ReadIniArray($iniarray, $section, $keylist[$i]) IF $keylist[$i] = "printer1" AND LEFT($value, 2) = "\\" AND NOT $ignoredefault = 1 $new_default_printer = $value ENDIF IF $keylist[$i] = "building_printers" AND $value = "false" IF $debug = 1 ? @Time + ": NP: building_printers=false Found..." ENDIF $building_printers = 0 ENDIF IF LEFT($keylist[$i], 7) = "printer" AND LEFT($value, 2) = "\\" $ret = ADDPRINTERCONNECTION($value) ; IF $debug = 1 ? @Time + ": NP: " + "ADDPRINTER( $value ) -- RETURN: " + $ret ENDIF ENDIF Next ENDIF endfunction ;This takes a computer name like AS123-4 and breaks it down into variables ;$prefix is characters from the start of the string until the first number OR the first dash ;$roomnumber is the first sequence of numbers before the dash ;$beforedash is everything before the dash ;$afterdash is everything after the dash ;$pcnumber is the numbers after the dash unless there are letters in the mix ;$pcnumber would be blank on AS123-B4 and on AS123-4B5 $pcnumber would stop after the 4 function DecipherComputerName($pcname) $dash = "" For $i = 1 to LEN($pcname) $char = MID($pcname, $i, 1) SELECT CASE $char = "-" $dash = "-" CASE IsNumeric($char) = 1 IF $dash = "" $roomnumber = $roomnumber + $char ELSE $pcnumber = $pcnumber + $char ENDIF CASE 1 SELECT CASE $dash = "" AND $roomnumber = "" $prefix = $prefix + $char CASE $dash = "" AND NOT $roomnumber = "" $roomnumber = $roomnumber + $char CASE $dash = "-" ;$i = LEN($pcname)+1 ENDSELECT ENDSELECT IF $dash = "" $beforedash = $beforedash + $char ELSE IF NOT $char = "-" $afterdash = $afterdash + $char ENDIF ENDIF Next endfunction ;this just counts the number of numbers in a string ;our student logons are all numbers so this is basically ;another way to detect a student logon function ContainsNumbers($teststr) $ContainsNumbers = 0 FOR $pos = 1 TO LEN($teststr) $char = SUBSTR($teststr, $pos, 1) SELECT case $char = "0" $ContainsNumbers = $ContainsNumbers+1 case $char = "1" $ContainsNumbers = $ContainsNumbers+1 case $char = "2" $ContainsNumbers = $ContainsNumbers+1 case $char = "3" $ContainsNumbers = $ContainsNumbers+1 case $char = "4" $ContainsNumbers = $ContainsNumbers+1 case $char = "5" $ContainsNumbers = $ContainsNumbers+1 case $char = "6" $ContainsNumbers = $ContainsNumbers+1 case $char = "7" $ContainsNumbers = $ContainsNumbers+1 case $char = "8" $ContainsNumbers = $ContainsNumbers+1 case $char = "9" $ContainsNumbers = $ContainsNumbers+1 ENDSELECT NEXT endfunction And here is a sample of the INI file I made up Code: ; ======================================================== ; FORMAT OF FILE ; ======================================================== ; ; [computername-1] ; printer1=\\server\printer name ;printer1 = Default Printer. If you don't want to set (or change) their default printer then start with printer2. ; printer2=\\server\printer name ;OPTIONAL: Extra network printers to add for all users (students included). Add more with printer3, printer4, etc. ; building_printers=false ;OPTIONAL: With this flag it will not add any printers from the [BUILDING_PRINTERS] section (applies to non-students only) ; ; [computername-1\username] ;User Name Override -- This user will only read this section and not the regular section for the computer ; printer1=\\server\printer name ; ; [computername-*] ;Printer Settings apply to all machines with the prefix (* must be preceeded by the first dash) ; printer1=\\server\mono printer 1 ;NOTE: You can override this for a single machine by specifying the actual computer name in another section. ; printer2=\\server\mono printer 2 ; printer3=\\server\color printer ; ; [BEFOREDASH-PC1,PC2,PC3] ;Match beforedash to after dash options. e.g. AJGUID-A,B,D or AJLIB-CIRC,OFFICE1,OFFICE2 ; ; [ABC100-1 through ABC120-1] ;Range such as [AJ100-1 through AJ120-1] or [AJLIB-1 through AJLIB-15] or [AJP14-* through AJP18-*]. ; ; The script checks for priority in the most specific to least: ; [computername\username] > [computername-*\username] > [computername] > [computername-*] > [ABC100-1 through ABC120-1] ; ; ======================================================== [BUILDING_PRINTERS] ;Printers that get added to all NON-Students unless building_printers=false is specified. printer1=\\AJH2\Library HPM551 (Color) printer2=\\AJH2\Library HPP3010#1 (Mono) printer3=\\AJH2\Library HPP3010#2 (Mono) printer4=\\AJH2\RICOH Copier Guidance printer5=\\AJH2\RICOH Copier Library printer6=\\AJH2\RICOH Copier Mail Rm printer7=\\AJH2\RICOH Special Ed printer8=\\AJH2\Rm 116 Office printer9=\\AJH2\Rm 136 Learning Center printer10=\\AJH2\Rm 215 Office printer11=\\AJH2\Rm 224 DE5100 (Color) printer12=\\AJH2\Rm 224 DE5300 (Mono) printer13=\\AJH2\Rm 230 DE5130cdn (Color) printer14=\\AJH2\Rm 230 DE5350dn (Mono) printer15=\\AJH2\Rm 242 Soc Studies Office printer16=\\AJH2\Rm 253 HP 4050 (Mono) printer17=\\AJH2\Rm 310 Office printer18=\\AJH2\Rm 324 DE5100 (Color) printer19=\\AJH2\Rm 324 DE5200 (Mono) printer20=\\AJH2\Rm 342 English Office printer21=\\AJH2\Rm C58 Classroom printer22=\\AJH2\Rm C58 DE3010cn (Color) printer23=\\AJH2\Rm P18 DE5350dn (Mono) printer24=\\AJH2\Rm S102B Science Office ;RANGES ;================= [AJ108-1 through AJ126-1] printer1=\\ajh2\Rm 116 Office [AJ136-*] printer1=\\ajh2\Rm 136 Learning Center [AJ128-1 through AJ157-1] printer1=\\ajh2\Rm 136 Learning Center [AJ208-1 through AJ227-1] printer1=\\ajh2\Rm 215 Office [AJ215-*] printer1=\\ajh2\Rm 215 Office [AJ228-1 through AJ265-1] printer1=\\ajh2\Rm 242 Soc Studies Office [AJ310-*] printer1=\\ajh2\Rm 310 Office [AJ308-1 through AJ329-1] printer1=\\ajh2\Rm 310 Office [AJ324-*] building_printers=false printer1=\\ajh2\Rm 324 DE5200 (Mono) printer2=\\AJH2\Rm 324 DE5100 (Color) [AJ330-1 through AJ363-1] printer1=\\ajh2\Rm 342 English Office [AJ351-*] printer1=\\ajh2\Rm 351 DE2330dn (Mono) [AJC51-1 through AJC60-1] printer1=\\ajh2\Rm C58 Classroom [AJC58-*] printer1=\\ajh2\Rm C58 Classroom printer2=\\AJH2\Rm C58 DE3010cn (Color) [AJGUID-1 through AJGUID-2] building_printers=false printer1=\\ajh2\RICOH Copier Guidance printer2=\\asd11\AJ Guid Dell 2330dn printer3=\\asd11\AJ Guid HP 1300n printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-C,B,J] building_printers=false printer1=\\asd11\AJ Guid HP 1300n printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid Dell 2330dn printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-F,G,H] building_printers=false printer1=\\asd11\AJ Guid Dell 2330dn printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid HP 1300n printer4=\\AJH2\RICOH Copier Mail Rm [AJHEALTH-*] printer1=\\asd11\JR Health Suite Dell 2330dn [AJLIB-1 through AJLIB-15] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) [AJLIB-16 through AJLIB-29] building_printers=false printer1=\\AJH2\Library HPP3010#2 (Mono) printer2=\\AJH2\Library HPP3010#1 (Mono) printer3=\\AJH2\Library HPM551 (Color) [AJLIB-30 through AJLIB-33] ;teacher computers in back printer1=\\AJH2\Library HPP3010#2 (Mono) [AJLIB-CIRC,OFFICE1,OFFICE2] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) printer4=\\AJH2\Library Lab DE2330 [AJM2-1 through AJM5-1] printer1=\\ajh2\Rm C58 Classroom [AJP14-* through AJP18-*] printer1=\\ajh2\Rm P18 DE5350dn (Mono) [AJS6-1 through AJS106-1] printer1=\\ajh2\Rm S102B Science Office ;SPECIFIC MACHINES ;================= [AJ111-1] ;guidance (has local) building_printers=true [AJ114-1] ;guidance (has local) building_printers=true [AJ210-1] ;reading chair (has local) building_printers=true [AJ216-1] ;guidance (has local) building_printers=true [AJ253-1] ;special ed room with printer in back printer1=\\ajh2\Rm 253 HP 4050 (Mono) [AJ316-1] ;guidance (has local) building_printers=true [AJ359-2] ;second computer in speech room printer1=\\ajh2\Rm 342 English Office [AJATTENDANCE] printer1=\\asd11\AJ Records Dell 2330dn [AJC59-1] ;art room (has local printer) building_printers=true [AJC100-1] printer1=\\ajh2\Rm 116 Office [AJC100-6] printer1=\\ajh2\Rm 116 Office [AJC300-1] printer1=\\ajh2\Rm 215 Office [AJRECEP] printer1=\\asd11\AJ Reception [AJRECORDS] printer1=\\asd11\AJ Records Dell 2330dn [AJS3-1] ;ISS - has local building_printers=true [AJS102B-1] printer1=\\AJH2\Rm S102B Science Office |
||||||||
|
|
|||||||
so, you are not actually giving them the closest printers, but the printers from their "home" building? |
||||||||
|
|
|||||||
Giving them the printer that is closest to the computer they are logging on to. As in "\\ash2\Rm 220B English Office" is the closest network printer to computer "AS220B-1". Edited my original post to include 2 UDFs I forgot to include. edit: The "building printers" section could be throwing you off-- every network printer in the building gets added to every teacher's computer so that they can print anywhere they want. The sections below that basically define the default printer (printer1) and any additional printers that students should get (as building printers aren't added for the students). |
||||||||
|
|
|||||||
ok... your ini-functions are rather complex for that purpose, given that simple few lines would do the logic in my opinion. thanks for sharing anyways... there might very well be something of use even for me in the future. |
||||||||
|
|
|||||||
If you're using my IniArray UDF, then the IniSections and IniKeys UDFs are redundant - you already have all of the data in the master array that was loaded. By using these extra UDFs, you're losing efficiency by loading the INI file into the array, then enumerating the INI file to locate data that's in your array. Code: $aData = IniArray('.\file.ini') ; List of sections For $S = 0 to UBound($aData, 2) 'Section: ' $aData[0, $S] ? ; list of keys in the current section $aKeyData = $aData[1, $S] ; put keys array into local var For $K = 0 to UBound($aKeyData, 2] 'Key / Data: ' $aKeyData[0, $K] ' / ' $aKeyData[1, $K] Next Next This UDF was updated just today, so you should pull the latest copy after 10pm EST tonight when it gets uploaded to our web site. The update improves performance for larger files and resoves a recently discovered bug that will appear when a section is defined in the file without any keys associated with it. You shouldn't have to do all the "UCase" commands as text comparisons are case insensitive unless you use a SetOption call to enable case sensitivity. It's generally good practice to leave the headers in the UDFs as they contain important reference material. You should never declare a GLOBAL var inside a function. Doing so will cause the var to be declared each time the function is called causing a Duplicate Definition error. I haven't reviewed the code in depth, but first blush it seems more complicated than necessary. If your computer name identifies the school and classroom along with a unique PC identifier, then a single config file could be used with one section per location and one key per classroom. If a classroom has multiple computers, then reverse the logic - Printer\share=room instead of room=Printer\Share. Search the values for DATA matching the room and the KEY will represent the printer share. You can then determine which preter was preferred. Glenn |
||||||||
|
|
|||||||
Originally Posted By: Lonkero ok... your ini-functions are rather complex for that purpose, given that simple few lines would do the logic in my opinion. There are over 400 computers in each school that the ini files takes are of, without having to specify 400 sections for 400 machines in each ini. The goal was to have as few sections as possible in the INI. The part of the ini I pasted was only a sample. Here are the two main INIs in their entirety, for the junior and senior high-- junior high... Code: ; ======================================================== ; FORMAT OF FILE ; ======================================================== ; ; [computername-1] ; printer1=\\server\printer name ;printer1 = Default Printer. If you don't want to set (or change) their default printer then start with printer2. ; printer2=\\server\printer name ;OPTIONAL: Extra network printers to add for all users (students included). Add more with printer3, printer4, etc. ; building_printers=false ;OPTIONAL: With this flag it will not add any printers from the [BUILDING_PRINTERS] section (applies to non-students only) ; ; [computername-1\username] ;User Name Override -- This user will only read this section and not the regular section for the computer ; printer1=\\server\printer name ; ; [computername-*] ;Printer Settings apply to all machines with the prefix (* must be preceeded by the first dash) ; printer1=\\server\mono printer 1 ;NOTE: You can override this for a single machine by specifying the actual computer name in another section. ; printer2=\\server\mono printer 2 ; printer3=\\server\color printer ; ; [ABC100-1 through ABC120-1] ;Range such as [AJ100-1 through AJ120-1] or [AJLIB-1 through AJLIB-15] or [AJP14-* through AJP18-*]. ; ; The script checks for priority in the most specific to least: ; [computername\username] > [computername-*\username] > [computername] > [computername-*] > [ABC100-1 through ABC120-1] ; ; ======================================================== [BUILDING_PRINTERS] ;Printers that get added to all NON-Students unless building_printers=false is specified. printer1=\\AJH2\Library HPM551 (Color) printer2=\\AJH2\Library HPP3010#1 (Mono) printer3=\\AJH2\Library HPP3010#2 (Mono) printer4=\\AJH2\RICOH Copier Guidance printer5=\\AJH2\RICOH Copier Library printer6=\\AJH2\RICOH Copier Mail Rm printer7=\\AJH2\RICOH Special Ed printer8=\\AJH2\Rm 116 Office printer9=\\AJH2\Rm 136 Learning Center printer10=\\AJH2\Rm 215 Office printer11=\\AJH2\Rm 224 DE5100 (Color) printer12=\\AJH2\Rm 224 DE5300 (Mono) printer13=\\AJH2\Rm 230 DE5130cdn (Color) printer14=\\AJH2\Rm 230 DE5350dn (Mono) printer15=\\AJH2\Rm 242 Soc Studies Office printer16=\\AJH2\Rm 253 HP 4050 (Mono) printer17=\\AJH2\Rm 310 Office printer18=\\AJH2\Rm 324 DE5100 (Color) printer19=\\AJH2\Rm 324 DE5200 (Mono) printer20=\\AJH2\Rm 342 English Office printer21=\\AJH2\Rm C58 Classroom printer22=\\AJH2\Rm C58 DE3010cn (Color) printer23=\\AJH2\Rm P18 DE5350dn (Mono) printer24=\\AJH2\Rm S102B Science Office ;RANGES ;================= [AJ108-1 through AJ125-1] printer1=\\ajh2\Rm 116 Office [AJ136-*] printer1=\\ajh2\Rm 136 Learning Center [AJ128-1 through AJ157-1] printer1=\\ajh2\Rm 136 Learning Center [AJ208-1 through AJ225-1] printer1=\\ajh2\Rm 215 Office [AJ215-*] printer1=\\ajh2\Rm 215 Office [AJ234-1 through AJ265-1] printer1=\\ajh2\Rm 242 Soc Studies Office [AJ310-*] printer1=\\ajh2\Rm 310 Office [AJ308-1 through AJ325-1] printer1=\\ajh2\Rm 310 Office [AJ324-*] building_printers=false printer1=\\ajh2\Rm 324 DE5200 (Mono) printer2=\\AJH2\Rm 324 DE5100 (Color) [AJ330-1 through AJ363-1] printer1=\\ajh2\Rm 342 English Office [AJ351-*] printer1=\\ajh2\Rm 351 DE2330dn (Mono) [AJC51-1 through AJC60-1] printer1=\\ajh2\Rm C58 Classroom [AJC58-*] printer1=\\ajh2\Rm C58 Classroom printer2=\\AJH2\Rm C58 DE3010cn (Color) [AJHEALTH-*] printer1=\\asd11\JR Health Suite Dell 2330dn [AJLIB-1 through AJLIB-15] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) [AJLIB-16 through AJLIB-29] building_printers=false printer1=\\AJH2\Library HPP3010#2 (Mono) printer2=\\AJH2\Library HPP3010#1 (Mono) printer3=\\AJH2\Library HPM551 (Color) [AJLIB-30 through AJLIB-33] ;teacher computers in back printer1=\\AJH2\Library HPP3010#2 (Mono) [AJM2-1 through AJM5-1] printer1=\\ajh2\Rm C58 Classroom [AJP14-* through AJP18-*] printer1=\\ajh2\Rm P18 DE5350dn (Mono) [AJS6-1 through AJS106-1] printer1=\\ajh2\Rm S102B Science Office ;SPECIFIC MACHINES ;================= [AJ111-1] ;guidance building_printers=true [AJ114-1] ;guidance building_printers=true [AJ210-1] ;reading chair building_printers=true [AJ216-1] ;guidance building_printers=true [AJ253-1] ;special ed room with printer in back printer1=\\ajh2\Rm 253 HP 4050 (Mono) [AJ316-1] ;guidance building_printers=true [AJ359-2] ;second computer in speech room printer1=\\ajh2\Rm 342 English Office [AJATTENDANCE] printer1=\\asd11\AJ Records Dell 2330dn [AJC59-1] ;art room (has local printer) building_printers=true [AJC100-1] printer1=\\ajh2\Rm 116 Office [AJC100-6] printer1=\\ajh2\Rm 116 Office [AJGUID-1] ;has local printer building_printers=false printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid HP 1300n printer4=\\asd11\AJ Guid Dell 2330dn printer5=\\AJH2\RICOH Copier Mail Rm [AJGUID-2] building_printers=false printer1=\\ajh2\RICOH Copier Guidance printer2=\\asd11\AJ Guid HP 1300n printer3=\\asd11\AJ Guid Dell 2330dn printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-B] building_printers=false printer1=\\asd11\AJ Guid HP 1300n printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid Dell 2330dn printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-C] building_printers=false printer1=\\ajh2\RICOH Copier Guidance printer2=\\asd11\AJ Guid HP 1300n printer3=\\asd11\AJ Guid Dell 2330dn printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-F] building_printers=false printer1=\\asd11\AJ Guid Dell 2330dn printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid HP 1300n printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-G] building_printers=false printer1=\\asd11\AJ Guid Dell 2330dn printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid HP 1300n printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-H] building_printers=false printer1=\\asd11\AJ Guid Dell 2330dn printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid HP 1300n printer4=\\AJH2\RICOH Copier Mail Rm [AJGUID-J] building_printers=false printer1=\\asd11\AJ Guid HP 1300n printer2=\\ajh2\RICOH Copier Guidance printer3=\\asd11\AJ Guid Dell 2330dn printer4=\\AJH2\RICOH Copier Mail Rm [AJC300-1] printer1=\\ajh2\Rm 215 Office [AJLIB-CIRC] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) printer4=\\AJH2\Library Lab DE2330 [AJLIB-OFFICE1] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) printer4=\\AJH2\Library Lab DE2330 [AJLIB-OFFICE2] building_printers=false printer1=\\AJH2\Library HPP3010#1 (Mono) printer2=\\AJH2\Library HPP3010#2 (Mono) printer3=\\AJH2\Library HPM551 (Color) printer4=\\AJH2\Library Lab DE2330 [AJRECEP] printer1=\\asd11\AJ Reception [AJRECORDS] printer1=\\asd11\AJ Records Dell 2330dn [AJS3-1] ;ISS - has local building_printers=true senior high... Code: ; ======================================================== ; FORMAT OF FILE ; ======================================================== ; ; [computername-1] ; printer1=\\server\printer name ;printer1 = Default Printer. If you don't want to set (or change) their default printer then start with printer2. ; printer2=\\server\printer name ;OPTIONAL: Extra network printers to add for all users (students included). Add more with printer3, printer4, etc. ; building_printers=false ;OPTIONAL: With this flag it will not add any printers from the [BUILDING_PRINTERS] section (applies to non-students only) ; ; [computername-1\username] ;User Name Override -- This user will only read this section and not the regular section for the computer ; printer1=\\server\printer name ; ; [computername-*] ;Printer Settings apply to all machines with the prefix (* must be preceeded by the first dash) ; printer1=\\server\mono printer 1 ;NOTE: You can override this for a single machine by specifying the actual computer name in another section. ; printer2=\\server\mono printer 2 ; printer3=\\server\color printer ; ; [ABC100-1 through ABC120-1] ;Range such as [AJ100-1 through AJ120-1] or [AJLIB-1 through AJLIB-15] or [AJP14-* through AJP18-*]. ; ; The script checks for priority in the most specific to least: ; [computername\username] > [computername-*\username] > [computername] > [computername-*] > [ABC100-1 through ABC120-1] ; ; ======================================================== [BUILDING_PRINTERS] ;Printers that get added to all NON-Students unless building_printers=false is specified. printer1=\\ASH2\Library#1 DE5310 (Mono) printer2=\\ASH2\Library#2 DE5310 (Mono) printer3=\\ASH2\Library DE5100 (Color) printer5=\\ASH2\Ricoh Copier Faculity Rm printer6=\\ASH2\Ricoh Copier Mail Rm printer7=\\ASH2\Rm 110 DE5100 (Color) printer8=\\ASH2\Rm 110 HP4200 (Mono) printer9=\\ASH2\Rm 121 HP4600 (Color) printer10=\\ASH2\Rm 129C Science Office printer11=\\ASH2\Rm 158 Classroom printer12=\\ASH2\Rm 159 DE3000 printer13=\\ASH2\Rm 209 Math Office printer14=\\ASH2\Rm 220 DE5100 (Color) printer15=\\ASH2\Rm 220 DE5300 (Mono) printer16=\\ASH2\Rm 220B English Office printer18=\\ASH2\Rm 222#1 DE5300 (Mono) printer19=\\ASH2\Rm 222#2 DE5300 (Mono) printer20=\\ASH2\Rm 224 DE5310 (Mono) printer21=\\ASH2\Rm 235 DE3000 printer22=\\ASH2\Rm 235 Special Ed Office printer23=\\ASH2\Rm 256 World Lang Office printer24=\\ASH2\Rm L4 DE5300 (Mono) printer25=\\ASH2\Rm M106 Music printer26=\\ASH2\Rm P215 Health printer27=\\ASH2\Rm S10 Classroom printer28=\\ASH2\Rm S101A Classroom printer29=\\ASH2\Rm S106 DE3000 ;RANGES ;================= [AS113-1 through AS118-1] printer1=\\ash2\Library#1 DE5310 (Mono) [AS124-1 through AS140-1] printer1=\\ash2\Rm 129C Science Office [AS129C-*] printer1=\\ash2\Rm 129C Science Office [AS150-1 through AS165-1] printer1=\\ash2\Rm 158 Classroom [AS159-*] printer1=\\ash2\Rm 159 DE3000 [AS201-1 through AS213-1] printer1=\\ash2\Rm 209 Math Office [AS219-1 through AS230-1] printer1=\\ash2\Rm 220B English Office [AS235-1 through AS239-1] printer1=\\ash2\Rm 235 Special Ed Office [AS243-1 through AS256-1] printer1=\\ash2\Rm 256 World Lang Office [ASA112-*] printer1=\\asd11\AS Lower Guid DE5300 [ASA115-*] printer1=\\asd11\AS Lower Guid Dell 2330dn [ASL4-*] building_printers=false printer1=\\ash2\Rm L4 DE5300 (Mono) printer2=\\ASH2\Library#1 DE5310 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASLIB-1 through ASLIB-25] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ASH2\Library#2 DE5310 (Mono) printer3=\\ASH2\Library DE5100 (Color) [ASLIB-26 through ASLIB-49] building_printers=false printer1=\\ASH2\Library#2 DE5310 (Mono) printer2=\\ash2\Library#1 DE5310 (Mono) printer3=\\ASH2\Library DE5100 (Color) [ASM100-1 through ASM106-1] printer1=\\ash2\Rm M106 Music [ASP212-1 through ASP217-1] printer1=\\ash2\Rm P215 Health [ASP215-*] printer1=\\ash2\Rm P215 Health [ASS3-1 through ASS10-1] printer1=\\ash2\Rm S10 Classroom [ASS101-1 through ASS113-1] printer1=\\ash2\Rm S101A Classroom [ASS101A-*] printer1=\\ash2\Rm S101A Classroom ;SPECIFIC MACHINES ;================= [AS120-1] printer1=\\AS119-1\Dell Open Print Driver (PCL XL) [AS121-1] printer1=\\ash2\Rm 121 HP4600 (Color) [AS209-2] printer1=\\ash2\Rm 209 Math Office [AS220B-1] printer1=\\ash2\Rm 220B English Office [AS222C-1] printer1=\\ash2\Rm 222#2 DE5300 (Mono) [AS233A-1] printer1=\\ASH2\Rm 235 Special Ed Office [AS233B-1] printer1=\\ASH2\Rm 235 Special Ed Office [ASA103-1] printer1=\\asd11\AS Upper Guid DE5300 [ASA118-1] printer1=\\ash2\Rm M106 Music [ASLIB-CIRC1] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ash2\Rm L4 DE5300 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASLIB-CIRC2] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ash2\Rm L4 DE5300 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASLIB-CIRC3] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ash2\Rm L4 DE5300 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASLIB-OFFICE1] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ash2\Rm L4 DE5300 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASLIB-OFFICE2] building_printers=false printer1=\\ash2\Library#1 DE5310 (Mono) printer2=\\ash2\Rm L4 DE5300 (Mono) printer3=\\ASH2\Library#2 DE5310 (Mono) printer4=\\ASH2\Library DE5100 (Color) [ASRECORDS-1] printer1=\\asd11\AS Records Hp 4200 [ASRECORDS-2] printer1=\\asd11\AS Upper Guid DE5300 [ASS4A-1] printer1=\\ash2\Rm S10 Classroom [ASS4B-1] printer1=\\ash2\Rm S10 Classroom [ASS10C-1] printer1=\\ash2\Rm S10 Classroom [ASS10-C] printer1=\\ash2\Rm S10 Classroom [ASS101A-1] building_printers=true [ASSEC-1] printer1=\\asd11\Sr Prin Dell 2330dn [AS107-1] building_printers=false printer2=\\ASH2\Ricoh Copier Mail Rm [ASA109-1] building_printers=true |
||||||||
|
|
|||||||
Originally Posted By: Glenn Barnas If you're using my IniArray UDF, then the IniSections and IniKeys UDFs are redundant - you already have all of the data in the master array that was loaded. By using these extra UDFs, you're losing efficiency by loading the INI file into the array, then enumerating the INI file to locate data that's in your array. I'm not sure what you mean here. I'm only reading the file once. Those two functions just take the default delimited string return from readiniarray function and puts it back into an array. Quote: This UDF was updated just today, so you should pull the latest copy after 10pm EST tonight when it gets uploaded to our web site. The update improves performance for larger files and resoves a recently discovered bug that will appear when a section is defined in the file without any keys associated with it. I put a link in my post the the UDF I'm using. It says it hasn't been updated since 8-2011. Quote: You shouldn't have to do all the "UCase" commands as text comparisons are case insensitive unless you use a SetOption call to enable case sensitivity. Yea, there are a lot of little things I do that are unnecessary, I know. Sometimes it's just for readability, but often it's because I write code primarily in PHP where it does matter a lot and it's just habit. Quote: It's generally good practice to leave the headers in the UDFs as they contain important reference material. I left links to all the UDFs in my post. The headers are quite long and I was already making a pretty lengthy post Quote: You should never declare a GLOBAL var inside a function. Doing so will cause the var to be declared each time the function is called causing a Duplicate Definition error. Good to know, I'll move it out. Quote: I haven't reviewed the code in depth, but first blush it seems more complicated than necessary. If your computer name identifies the school and classroom along with a unique PC identifier, then a single config file could be used with one section per location and one key per classroom. See my my reply to Lonkero above. I had only pasted a snipit of my full INI. The goal was to have as few sections in the INI as possible. |
||||||||
|
|
|||||||
OK - I misread the source of those two functions.. they're fine. Most of my UDF posts reference this and many of the older farts around here know, but my web site always hosts the most current copy of the UDFs we use in production coding. I rewrote the PostPrep utility to run from a command line, and it has the ability to convert an entire folder's worth of UDFs to HTML format. Our web management server runs a task at 10pm nightly that converts all of our UDFs to HTM, so any updates will appear nightly. I do give a heads-up on KORG for some of the more popular UDFs and even update some of them here, but it's a good idea to always check our site for the most current versions.. Click the IT Resources menu, then Kixtart. The UDF Library link is in the middle of that page. There are 141 items on that link, including 4 "libraries" - collections of related UDFs for database access, WSUS management, task scheduler control, an MS Excel manipulation. The version string for IniArray() is Code: ;;VERSION 1.0 / 2011-08-02 ;; 1.1 / 2013-04-17 - improve file load on large files, bugfix My reference to UCase didn't indicate it was wrong or bad form, just that it was unnecessary in Kix. Before you just move the global declaration out of your UDF, take a look at the tcLib UDF Library, specifically the tcInit() function for an alternate method. This function declares global vars only if they don't already exist, allowing the first call to tcInit to initialize the environment and preload the arrays, following calls only preload the arrays. I still think your method is complex. Tell me how your computer naming scheme works and I might be able to offer some ways to simplify. Right off the bat, with computer/user definitions, you might be able to do something like Code: ; configuration for a specific computer, with user-specific default printers. Unlisted users default to Printer1 [computer 1] fred=Printer2 mary=Printer3 ; printers map for all users, printer1 is set default unless a user-specific override is defined Printer1=printer UNC Printer2=printer UNC Printer3=printer UNC ; disable mapping of all printers - default is to map Building_Printers=No Think about the data before the code.. realize that an INI value can hold many data items: Code: [SECTION] Value=D1,d2,"last, first",d5 Code: $aRecordData=CSV(ReadIniArray($Array, $Section, $Value)) Tell us more about the layout of the district and we'll be able to offer some specific ideas to make this nearly bullet-proof for you. Glenn |
||||||||
|
|
|||||||
I was about to ask how the INISections and INIKeys UDFs were inefficient (certainly they are redundant) but it seems that it got sorted out. I think it's a matter of scripting preference regarding the UDFs. Personally I don't like dealing with multidimension arrays. Using the functions allows you to "See and Say" what is happening with all those values. Here is Glenn's example using the UDFs, done on the fly, untested. Code: $aData = IniArray('.\file.ini') For each $S in INISections($aData) 'Section: ' + $S ? For each $K in INIKeys($aData,$S) 'Key / Data: ' + $K + ' / ' + ReadINIArray($aData,$S, $K) ? Next Next |
||||||||
|
|
|||||||
Wow has it already been almost 2 years since you wrote those INIArray functions? |
||||||||
|
|
|||||||
Allen - yeah, it was a crazy day and I thought I saw ReadProfileString in those functions, which would have performed duplicate INI reads.. my mistake. Shane - time flies, whether you're having fun or not! Note the recent code update on this UDF.. I'm getting slightly better performance loading the array with LARGE files and ironically slightly worse performance with tiny (1-2K) files, but we're talking 32-48ms worst on small files, but 100-500+ ms gains on larger files. Also, the worst-case is an estimate because the resolution of Kix @MSECS macro I used for timing is 16ms. More importantly, the new version corrects a bug that can occur when your INI file contains a section with no keys. Technically, not valid, but possible with hand-written INI files. The old version loaded the section with no keys and threw an error when you asked for a key. The new code ignores empty sections on read. I just updated the Universal Login Script to use IniArray and shaved 600ms off of a typical config, bringing production execution time for the script to under 2 seconds! Script overhead is just 750ms - the time it takes to load and process an empty config. Glenn |
||||||||
|
|
|||||||
Just a remark about performance with ini file : personnally, i am still using ReadProfileString but if i know the file to read is on a network share (like netlogon), i copy the file in a temporary local file and all following actions are done on the local file. Why i do like that ? the windows api is quite efficient but it is a monolithic operation. The ini file is read in memory, list of section, list of keyword or values are read and then, the file is closed and memory freed. when this is done on a local file, the first time, the file is loaded in the local disk cache and next actions are done with the copy in the cache. This is very fast. When the same thing is done on a network file, there is no cache. for each action, the all file is read through the network. Even if the network is very fast, this is slower than a local read. Suppose you have to read 10 sections with 10 keys in a 10 kb file. - locally, you read 10kb one time on disk - from the network, you read 10 x 10 x 10kb (1 Mb). This is much more slower !!! initially, in my login script, i read network file. it was really slow specially for WAN workstation. When i changed the script to create a temporay local file and, performances increased significantly. |
||||||||
|
|
|||||||
Christophe, That's part of the point of the IniArray UDF suite. It uses standard File I/O to load the entire INI file into an array once rather than using multiple Read/WriteProfileString functions. The other purpose of this UDF is that it removes the 32/64K size limit of INI files. My earlier reference of a 600ms performance improvement was moving from ReadProfileString on a locally cached file to loading the same local file into IniArray and using ReadIniArray calls instead. On a WAN, local caching of a 12K file before loading into IniArray will save between 0.4 and 1.3 seconds, depending on WAN speed. Directly reading the INI on the server with ReadProfileString is much worse and local caching has been known to knock off 15-20 seconds! This is why our Universal Login Script has been caching the INI file on local disk since 2006. The IniArray UDFs just take it to the next level, managing all INI functions in RAM. Glenn |
||||||||
|
|
|||||||
FYI - Allen reported a bug in the IniArray package that occurs when you delete a key two or more times. This is an unusual situation (likely due to some wacky stuff Allen's doing ) That bug's been fixed, but the problem brough to light some other issues with potentially invalid situations (empty section in INI, for example). I've run some significant tests on the current version and should have an update tonight or tomorrow. I'm waiting for Allen to test in his large INI app. Bottom line, if you use IniArray and the related functions, please be on the lookout for the newest version (1.2) in the next 48 hours or so. Glenn |
||||||||
|
|
|||||||
I only found the bug in INIArray because I had a bug in my code. Nothing wacky going on. |
||||||||
|
|
|||||||
Aren't bugs wacky? |
||||||||
|
|
|||||||
Glenn, i agree that it is a very bad idea to use readprofilestring with a network file. This is why I first copy network file to a local temp file and use the local file. i have made a test of performance with your functions and with ReadProfileString on local files of several size. Here is my code : Code: break on dim $inifile, $tmpfile, $ini, $arrSections, $arrKeys, $section, $key, $d1, $d2 $arrInifiles = @scriptdir+"\test\file000.ini", @scriptdir+"\test\file001.ini", @scriptdir+"\test\file002.ini", @scriptdir+"\test\file003.ini", @scriptdir+"\test\file004.ini", @scriptdir+"\test\file005.ini", @scriptdir+"\test\file006.ini", "" right( " size",8 ) right( " time1",8 ) right( " time2",8 ) " filename" ? for each $inifile in $arrInifiles if $inifile $tmpfile = @scriptdir+"\"+@ScriptName+".tmp" COPY $inifile $tmpfile $size = GetFileSize($tmpfile) ;-- read with ini functions -- $start = @TICKS $ini = IniArray( $tmpfile ) $arrSections = IniSections($ini) for each $section in $arrSections $arrKeys = IniKeys( $ini, $section ) for each $key in $arrKeys $value = ReadIniArray( $ini, $section, $key ) next next $d1 = @TICKS - $start ;-- read with ReadProfileString -- $start = @TICKS $arrSections = ReadProfileString( $tmpfile, "", "" ) $arrSections = split($arrSections, chr(10) ) for each $section in $arrSections if $section $arrKeys = ReadProfileString( $tmpfile, $section, "" ) $arrKeys = split($arrKeys, chr(10) ) for each $key in $arrKeys if $key $value = ReadProfileString( $tmpfile, $section, $key ) endif next endif next $d2 = @TICKS - $start right( " "+$size,8 ) right( " "+$d1,8 ) right( " "+$d2,8 ) " " $inifile ? DEL $tmpfile endif next ;-- insert your ini functions here I have been very surprised because on my workstation, code with ReadProfileString is always faster (Windows XP SP3 and never constated problem of file size) !!! Code: size time1 time2 filename 91 0 0 .\test\file000.ini 2019 0 0 .\test\file001.ini 7553 47 15 .\test\file002.ini 34527 485 47 .\test\file003.ini 69058 1078 140 .\test\file004.ini 103587 1844 219 .\test\file005.ini 138116 2735 344 .\test\file006.ini First, i copy the ini file in a temp file. so, ini file is the local cache disk. I think the difference of speed is due to many calls to functions with array that can be large. As variables are passed by value, each call duplicates the array in memory. With large ini file this can be much slower than call to ReadProfileString. One of the first steps of my login script is to do a robocopy from \\netlogon to a local temp directory. Next, all scripts are executed from local directory. We have the same login script for about 6000 users. At the first connection, the netlogon is copied locally. At the following connection, a new robocopy copies only changed files (not often). This is fast and efficient, specially for WAN workstations. I will make other tests tomorrow at the office on several types of workstations to see if results are reproductible... Now, it's time to sleep in France |
||||||||
|
|
|||||||
the difference most likely comes from the fact that the first read is done from the disk and AV is possibly interfering with it too. you should do a simple: Code: $trash=open(1,$tmpfile) do $trash=readline() until @error $trash=close(1) to make sure the file is indeed cached before timing the difference. |
||||||||
|
|
|||||||
may be AV is impacting the result but i start by a COPY to tmpfile so this file should already be in the cache and should have been scanned by the AV. i tried to add your line in my code. the result is Code: size time1 time2 filename 91 0 0 .\test\file000.ini 2019 0 0 .\test\file001.ini 7553 47 16 .\test\file002.ini 34527 484 47 .\test\file003.ini 69058 1094 141 .\test\file004.ini 103587 1859 250 .\test\file005.ini 138116 2796 375 .\test\file006.ini This is very near from the previous results !!! if i have free time, i'll try to test a new version of ini functions based on a "ini handle" in spite of array of strings. The idea is that IniArray loads data in a global internal array and returns a handle. Next calls to ReadIniArray, IniSections or IniKeys just used this handle. I hope duplicating of an integer is much more faster than duplicating an array. Theorically, impact is null on functions but navigation in the inifile with access to the 3D array becomes impossible (this should not be used). This solution also needs to add an IniClose function to free memory array and handle. |
||||||||
|
|
|||||||
I have a version that does indirect references to global arrays.. I had several people using this version so wanted to address some of the issues they had before updating/releasing the other version. The interface is the same, except you pass the name of the array, not the array itself. Glenn |
||||||||
|
|
|||||||
happy to see that we have similar idea |
||||||||
|
|
|||||||
I put a lot of effort into the quality and performance of our login script - one of the reasons it's used at so many global companies and educational facilities. In a recent deployment at a financial company, we brought the Citrix logon script time down from nearly 30 seconds to under 4.5, and the less complex desktop login script to under 2 seconds. While many/most of the UDFs it uses are published on our web site, I don't always put those that provide us with a strong competetive edge into the public domain right away! Glenn |
||||||||
|
|
|||||||
so... what you are saying is, that you knew this one was actually slow and maybe even slower than readprofilestring() but did not say it that way, as it gave you competitive edge? I do have to say that sounds... wacky... |
||||||||
|
|
|||||||
The purpose of the IniArray UDF was not an alternative to Read/WriteProfileString, but specifically to overcome the size limitations of INI files. I wrote this for Shane some time ago and Allen's been using it as well. In my testing, the moderately sized INI files used by my login script saw performance gains over repetetive ReadProfileString calls. I developed another version of this that uses Global arrays that is quite a bit faster, but I haven't published it because - while it works for my login script, it isn't very generic. And - since my login script is a closed-source commercial product, of course I don't publish every one of the UDFs it uses! I am working on a generic version that uses global arrays, but that will alter the syntax of the read/write calls (eliminating the data source name) and will require careful management of the INI array if multiple data sources are needed. The published version does not have the limitation of a single, globally declared data source. Glenn |
||||||||
|
|
|||||||
OK - I'm starting to wonder about the performance of arrays in Kix... I modified the IniArray library to use a single, global array. No more passing arrays to/from the functions. While I can determine that this is slightly faster than passing the arrays, it is (surprisingly) nowhere near the direct read performance of ReadProfileString. Taking your test code and my new function with Global arrays, I get the following results. The one difference in my code is that I separated out the array load time (LTime) from the ReadIniArray enumerations. Code: size LTime time1 time2 filename 1199 16 0 16 .\test1.ini 7131 31 141 62 .\test2.ini 19989 47 687 188 .\test3.ini 63231 141 4375 890 .\test4.ini 91229 219 12062 1703 .\test5.ini Code: size LTime time1 time2 filename 1199 0 16 0 .\test1.ini 7131 16 141 78 .\test2.ini 19989 16 391 187 .\test3.ini 63231 78 2484 1032 .\test4.ini 91229 109 7125 2109 .\test5.ini Win-8 PC on Network drive (100Mbps) Code: size LTime time1 time2 filename 1199 0 16 47 .\test1.ini 7131 31 140 438 .\test2.ini 19989 62 688 2156 .\test3.ini 63231 156 4360 16625 .\test4.ini 91229 250 12109 34672 .\test5.ini Code: size LTime time1 time2 filename 1199 16 0 47 .\test1.ini 7131 15 141 328 .\test2.ini 19989 47 562 1000 .\test3.ini 63231 140 2704 4875 .\test4.ini 91229 203 7047 8985 .\test5.ini I think I'll drop the Global Array model for now since the performance benefit is slight. The IniArray library has benefit for network based I/O and for larger files. Glenn Here's the modified test code and global array version of IniArray: Code: Break On Dim $ $ = SetOption('Explicit', 'On') $ = SetOption('WrapAtEOL', 'On') $ = SetOption('NoVarsInStrings', 'On') If Not Exist('.\Test1.ini') 'Generating test files' GenIni('.\Test1.ini', 10, 5) '!' GenIni('.\Test2.ini', 50, 8) '!' GenIni('.\Test3.ini', 100, 15) '!' GenIni('.\Test4.ini', 250, 18) '!' GenIni('.\Test5.ini', 500, 12) 'Done!' ? ? EndIf 'Press a key to continue ' Get $ ? dim $arrInifiles, $inifile, $tmpfile, $ini, $arrSections, $arrKeys, $section, $key, $d0, $d1, $d2, $Size, $Start, $Value $arrInifiles = ".\test1.ini", ".\test2.ini", ".\test3.ini", ".\test4.ini", ".\test5.ini" right( " size",8 ) right( " LTime",8 ) right( " time1",8 ) right( " time2",8 ) " filename" ? for each $inifile in $arrInifiles if $inifile $tmpfile = @scriptdir+"\"+@ScriptName+".tmp" COPY $inifile $tmpfile $size = GetFileSize($tmpfile) ;-- read with ini functions -- $start = @TICKS $ini = IniArray($tmpfile) $d0 = @TICKS - $Start $start = @TICKS $arrSections = Split(ReadIniArray(''), Chr(10)) for each $section in $arrSections $arrKeys = Split(ReadIniArray($section), Chr(10)) for each $key in $arrKeys $value = ReadIniArray($section, $key ) next next $d1 = @TICKS - $start ;-- read with ReadProfileString -- $start = @TICKS $arrSections = ReadProfileString( $tmpfile, "", "" ) $arrSections = split($arrSections, chr(10) ) for each $section in $arrSections if $section $arrKeys = ReadProfileString( $tmpfile, $section, "" ) $arrKeys = split($arrKeys, chr(10) ) for each $key in $arrKeys if $key $value = ReadProfileString( $tmpfile, $section, $key ) endif next endif next $d2 = @TICKS - $start right( " "+$size,8 ) right( " "+$d0,8 ) right( " "+$d1,8 ) right( " "+$d2,8 ) " " $inifile ? DEL $tmpfile endif next Function GenIni($_File, $_S, $_MKey) Dim $_, $_X, $_Y Dim $_Section, $_Key, $_Data Del $_File SRnd(@MSECS) For $_X = 1 to $_S ; Sections For $_Y = 1 to 1 + Rnd($_MKey) ; Keys $_Section = 'SECTION_' + Right('0000' + $_X, 4) $_Key = 'Key_' + Right('00' + $_Y, 2) $_Data = Left('The quick brown fox jumped over the lazy dog.', Rnd(30)) $_ = WriteProfileString($_File, $_Section, $_Key, $_Data) Next If $_X Mod 50 = 0 '.' EndIf Next Exit 0 EndFunction ;; ;;====================================================================== ;; ;;FUNCTION IniArray() ;; ReadIniArray() - subfunction for reading array ;; WriteIniArray() - subfunction for writing array ;; ;;ACTION Reads INI file into an array; ;; Writes array to INI format file & reloads the array with fresh data ;; ReadIniArray() reads a Section:Key:Data set from the array ;; WriteIniArray() writes a Section:Key:Data set to the array ;; ;;AUTHOR Glenn Barnas ;; ;;VERSION 1.0 / 2011-08-02 ;; 1.1 / 2013-04-17 - improve file load on large files ;; 1.2 / 2013-04-20 - bugfixes: ;; Read empty section or file, write empty section, ;; WriteIniArray() - fix error if duplicate write of null data ;; Added Options to IniArray for Write to add blank ;; lines between sections ;; ;;SYNTAX IniArray(filename, Action [,options]) ;; ;;PARAMETERS filename - REQUIRED - the name of the INI file to read/write ;; ;; action - OPTIONAL - "R" or "W" for Read or Write - Defaults to Read ;; ;; options - OPTIONAL - bitwise settings to control output formatting ;; Value Mode Description ;; 1 Write Adds blank lines between Sections when true ;; 2 Write Suppress the array reload on write, useful when ;; no further INI manipulation is planned. ;; ;;REMARKS Reads an entire INI file into an array for processing. There is no limit ;; on the size of the INI file, other than any Kix limits on general file sizes. ;; On read, blank lines and comments (lines that begin with ";" or "#") are ;; ignored. On write, only sections that contain data/value pairs are written, ;; mimicing the action of WriteProfileString(). Similarly, only Keys that ;; have non-null values are written. ;; ;; The global array called $a_INIDATA_ is used for operations and will be declared ;; by the first call to IniArray if it was not previously defined. ;; ;; The secondary functions ReadIniArray() and WriteIniArray() make using the ;; IniArray() UDF as easy to use as ReadProfileString() and WriteProfileString(), ;; simply requiring calls to IniArray to load and then save the INI file. The ;; Read and Write sub-functions use the same syntax as the ProfileString ;; functions but reference the array instead of the INI file. ;; ;; NOTE: During array manipulation, deleted records are set to null and not reused. ;; Similarly, when a new key/data pair is added, deleted array items are ;; not reused. When the array is written to disk, the null records are skipped. ;; When the file is again read, the array will only contain valid data with no ;; empty records. ;; WRITING an array causes a RE-READ operation, cleaning the empty records. ;; ;; NOTE: IMPORTANT - When using WriteIniArray() to create a new Ini-array, you MUST ;; first declare the array via Dim $a_INIARRAY_[1, 0]. This is not necessary ;; if you read an INI file first with IniArray(filename). ;; ;;RETURNS IniArray: Returns 1 on success or 0 on failure ;; Populates a two-dimensional array of two-dimensional arrays. The first element ;; is the section name, the second element is a two-dimensional array consisting of ;; the key/data pairs. ;; ;; ReadIniArray: returns the data specified by the section/value pair ;; WriteIniArray: Returns 1 on success or 0 on failure ;; Updates the global array with the section/value/data provided ;; ;; Consider the following simple INI file: ;; [COLORS] ;; Apple=Red ;; Lime=Green ;; [TASTES] ;; Apple=sweet ;; Lime=sour ;; ;; The call $Rc = IniArray('inifile.ini') would populate the $a_INIDATA_ array ;; that contained the following values: ;; $a_INIDATA_[0,0] contains "COLORS" ;; $a_INIDATA_[1,0] contains an array of data/value pairs from this section ;; - extract with $aData = $a_INIDATA_[1,0] ;; $aData[0,0] contains "Apple"; $aData[1,0] contains "Red" ;; $aData[0,1] contains "Lime"; $aData[1,1] contains "Green" ;; ;; $a_INIDATA_[0,1] contains "TASTES" ;; $a_INIDATA_[1,1] contains Array of data/value pairs from this section ;; - extract with $aData = $a_INIDATA_[1,1] ;; $aData[0,0] contains "Apple"; $aData[1,0] contains "Sweet" ;; $aData[0,1] contains "Lime"; $aData[1,1] contains "Sour" ;; ;; ; the following would return "Sweet", just like ReadProfileString ;; $Taste = ReadIniArray('TASTES', 'Apple') ;; ;; ;;DEPENDENCIES none ;; ;;TESTED WITH W2K, WXP, W2K3, W2K8, W7 ;; ;;EXAMPLES INI FILE READ: ;; $Rc = IniArray('testfile.ini') ;; 'File contains ' UBound($a_INIDATA_) + 1 ' sections' ? ;; ;; ENUMERATE: ;; For $I = 0 to UBound($a_INIDATA_, 2) ;; $aData = $a_INIDATA_[1, $I] ;; ;; 'Section: ' $a_INIDATA_[0, $I] ' contains ' UBound($aData, 2) + 1 ' Data/Value elements' ? ;; ' Press a key: ' get $ ? ;; ;; For $J = 0 to UBound($aData, 2) ;; $J ': ' $aData[0, $J] ' = ' $aData[1, $J] ? ;; Next ;; Next ;; ;; ELEMENT READ: ;; ; Return a specific value ;; $Value = ReadIniArray('SectionName', 'keyname') ;; ; Return an array of all section names ;; $aSections = Split(ReadIniArray(''), Chr(10)) ;; ; Return an array of Keys in a specific section ;; $aKeys = Split(ReadIniArray('SectionName'), Chr(10)) ;; ;; ELEMENT WRITE/UPDATE: ;; ; Write a key/value pair in the named section ;; $Rc = WriteIniArray('SectionName', 'keyname', 'Data') ;; ;; ELEMENT DELETE: ;; ; Remove the named key from the defined section ;; $Rc = WriteIniArray('SectionName', 'keyname') ;; ;; SECTION DELETE: ;; ; Remove all key/value pairs from the section, deleting the section ;; $Rc = WriteIniArray('SectionName') ;; ;; INI FILE WRITE: ;; ; Flush the array to the file and then reload the array ;; $Rc = IniArray('testfile.ini', "W") ; Function IniArray($_fSrcFile, OPTIONAL $_DataWrite, OPTIONAL $_Options) Dim $_ ; temp var Dim $_Fp ; file pointer Dim $_C, $_I, $_J ; index pointers Dim $_AddLine, $_NoRead ; Option Vars Dim $_Sect ; Section Name Dim $_aDat ; Data pair array Dim $_aData[1, 499] ; Section & Data Arrays $_NoRead = 0 ; perform read after write If $_Options If $_Options & 1 $_AddLine = @CRLF EndIf If $_Options & 2 $_NoRead = 1 ; write & exit w/o re-reading EndIf EndIf ; Obtain a File Handle for Read or Write operations ; ============================================================ $_Fp = FreeFileHandle ; locate an available file handle If Not $_Fp Exit 1 ; none available - exit EndIf ; WRITE: verify that we have properly formatted data ; ============================================================ If $_DataWrite = 'W' ; Write operation If VarType($a_INIDATA_) < 8192 ; Not an array - exit! Exit 87 EndIf Del $_fSrcFile ; delete any pre-existing file $_ = Open($_Fp, $_fSrcFile, 5) ; open the file for write/create If @ERROR ReDim $a_INIDATA_[1, 0] ; create empty array Exit @ERROR ; exit if error opening EndIf ; Write the section data. If no data exists in the section, do not write anything! For $_I = 0 to UBound($a_INIDATA_, 2) $_Sect = $a_INIDATA_[0, $_I] ; Section name to write $_aData = $a_INIDATA_[1, $_I] ; Data array If UBound($_aData, 2) > -1 ; create Sect and write data if data is present $_ = '[' + $_Sect + ']' + @CRLF ; section name $_C = 0 For $_J = 0 to UBound($_aData, 2) ; key/data pairs If $_aData[1, $_J] ; only write keys that have non-null data $_C = 1 $_ = $_ + $_aData[0, $_J] + '=' + $_aData[1, $_J] + @CRLF EndIf Next If $_C $_ = WriteLine($_Fp, $_ + $_AddLine) ; write the output line EndIf If @ERROR $_ = Close($_Fp) ; close the file Exit @ERROR EndIf ; exit if error writing EndIf Next $_ = Close($_Fp) ; close the file ReDim $_aData[1, 0] ; clear array If $_NoRead exit 0 ; exit here without a re-read of the freshly written data EndIf EndIf ; declare the INI array if undefined If Not IsDeclared($a_INIDATA_) Global $a_INIDATA_[1,0] EndIf ; READ: Load the ini file into an array ; ============================================================ $_I = -1 $_J = -1 ; Initialize index pointers $_ = Open($_Fp, $_fSrcFile, 2) ; open the file for read If @ERROR ReDim $a_INIDATA_[1, 0] ; return an empty array Exit @ERROR ; exit if error opening EndIf ReDim $a_INIDATA_[1, 499], $_aData[1, 499] ; Prep Section & Data Arrays for Read $_ = Trim(ReadLine($_Fp)) ; read INI, removing leading/trailing spaces While Not @ERROR ; loop through the file contents If Left($_, 1) = '[' ; found a section If $_I >= 0 ; process prior section data, if any If $_J >= 0 ReDim Preserve $_aData[1, $_J] ; trim data array to size $a_INIDATA_[1, $_I] = $_aData ReDim $_aData[1, 499] ; create the data array for the new section $_J = -1 Else $_I = $_I - 1 ; ignore empty sections EndIf EndIf $_I = $_I + 1 ; increment the section index If $_I Mod 499 = 0 And $_I > 0 ReDim Preserve $a_INIDATA_[1, $_I + 500] ; increase the section input buffer EndIf $a_INIDATA_[0, $_I] = Split(SubStr($_, 2), ']')[0] Else If Not InStr(';#', Left($_, 1)) And Len($_) > 2 $_aDat = Split($_, '=') ; break into array $_J = $_J + 1 ; increment the data index If $_J Mod 499 = 0 And $_J > 0 ReDim Preserve $_aData[1, $_J + 500] ; increase the key input buffer EndIf $_aData[0, $_J] = $_aDat[0] ; Store the value $_aData[1, $_J] = $_aDat[1] ; Store the data EndIf EndIf $_ = Trim(ReadLine($_Fp)) ; remove leading/trailing spaces Loop ; done with input data $_ = Close($_Fp) ; close the file $_ = 2 ; prep for Not Found exit ; process the last/only section If $_I >= 0 If $_J >= 0 ReDim Preserve $_aData[1, $_J] ; trim data array to size $a_INIDATA_[1, $_I] = $_aData Else ReDim Preserve $_aData[1, 0] $a_INIDATA_[1, $_I] = $_aData EndIf ReDim Preserve $a_INIDATA_[1, $_I] ; trim section array to size $_ = 0 EndIf Exit $_ ; exit success EndFunction Function ReadIniArray(OPTIONAL $_Section, OPTIONAL $_Key) Dim $_I, $_J ; Index pointers Dim $_aData, $_aSrc ; Array of Key/Data pairs Dim $_Cmd Dim $_S Dim $_ ; exit immediately if the data format is invalid If VarType($a_INIDATA_) < 8192 ; data but not an array - exit! Exit 87 EndIf ; Get the array size $_S = UBound($a_INIDATA_, 2) If $_S <= 0 Exit 87 EndIf Dim $_aSectIdx[$_S] ; Section Index Array ; Create a section index array For $_I = 0 to $_S $_aSectIdx[$_I] = $a_INIDATA_[0, $_I] Next ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] If Not $_Section $ReadIniArray = Join($_aSectIdx, Chr(10)) Exit 0 EndIf ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) If $_I < 0 Exit 2 EndIf ; section not found - Exit $_aData = $a_INIDATA_[1, $_I] ; Extract the key/value array ; Create a Key index for the requested section Dim $_aKeyIdx[UBound($_aData, 2)] For $_J = 0 to UBound($_aData, 2) $_aKeyIdx[$_J] = $_aData[0, $_J] Next ; If the Key is null, return a delimited string of Keys [same as ReadProfileString(file, section)] If Not $_Key $ReadIniArray = Join($_aKeyIdx, Chr(10)) Exit 0 EndIf ; Search the index for a Key $_J = aScan($_aKeyIdx, $_Key) If $_J < 0 Exit 2 EndIf ; Key not found $ReadIniArray = $_aData[1, $_J] Exit 0 EndFunction Function WriteIniArray($_Section, OPTIONAL $_Key, OPTIONAL $_Data) ; exit immediately if the data format is invalid If VarType($a_INIDATA_) < 8192 ; data but not an array - exit! Exit 87 EndIf Dim $_aSectIdx[UBound($a_INIDATA_, 2)] ; Section Index Array Dim $_I, $_J ; Index pointers Dim $_aData ; Key/Data array ; Create a section index array For $_I = 0 to UBound($a_INIDATA_, 2) $_aSectIdx[$_I] = $a_INIDATA_[0, $_I] Next ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) ; If the section does not exist and Keydata is present - add the section and key/value pair (new Sect:Key:Data) If $_I < 0 ; section not found - Add if Data is present If $_Key And $_Data $_I = 0 ; default to empty array If $a_INIDATA_[0, 0] $_I = UBound($a_INIDATA_, 2) + 1 ; find next section index EndIf ReDim Preserve $a_INIDATA_[1, $_I] ; Add new section ReDim $_aData[1, 0] ; create data array $_aData[0,0] = $_Key ; key $_aData[1,0] = $_Data ; data $a_INIDATA_[0, $_I] = $_Section ; section name $a_INIDATA_[1, $_I] = $_aData ; section data Else Exit 0 ; nothing to do EndIf Else ; the section does exist, locate the Key ; If the Key is null, delete the section (delete Section) If Not $_Key ReDim $_aData[1, 0] ; create empty keys array $a_INIDATA_[1, $_I] = $_aData ; write to section $a_INIDATA_[0, $_I] = '' ; write null section name Else $_aData = $a_INIDATA_[1, $_I] ; Extract the key/value array ; Create a Key index for the requested section Dim $_aKeyIdx[UBound($_aData, 2)] For $_J = 0 to UBound($_aData, 2) $_aKeyIdx[$_J] = $_aData[0, $_J] ; create the index Next $_J = aScan($_aKeyIdx, $_Key) ; find the key ; If the key does not exist, add the key/data (new Key/data) If $_J < 0 If $_Data $_aData = $a_INIDATA_[1, $_I] ; array of key/data pairs $_J = UBound($_aData, 2) + 1 ; find next key index ReDim Preserve $_aData[1, $_J] ; create data array $_aData[0, $_J] = $_Key ; key $_aData[1, $_J] = $_Data ; data $a_INIDATA_[1, $_I] = $_aData ; section data Else Exit 0 ; nothing to do (no data) EndIf Else ; the key exists - either Update or Delete ; if the data is not null, write the new data (update key) If $_Data $_aData[1, $_J] = $_Data ; If the data is null, write empty key and data values (delete key) Else $_aData[0, $_J] = '' ; delete key $_aData[1, $_J] = '' ; clear data value EndIf $a_INIDATA_[1, $_I] = $_aData ; update the array EndIf EndIf EndIf Exit 0 EndFunction |
||||||||
|
|
|||||||
that is indeed weird. was thinking about checking the readiniarray() syntax for obvious time hogs, but the UDF is not in our library. |
||||||||
|
|
|||||||
found it. and now found it in your script too |
||||||||
|
|
|||||||
hmm... I will try a different design if I don't fall asleep too fast. |
||||||||
|
|
|||||||
did try couple different ones... it's still just as slow. I eliminated the multidim array, in hopes of some improvement. did a separate index array for the keys... still not improving. I am starting to think it might not actually have anything to do with arrays but UDF processing itself. here is my tweaked one... Code: Break On Dim $ $ = SetOption('Explicit', 'On') $ = SetOption('WrapAtEOL', 'On') $ = SetOption('NoVarsInStrings', 'On') If Not Exist('.\Test1.ini') 'Generating test files' GenIni('.\Test1.ini', 10, 5) '!' GenIni('.\Test2.ini', 50, 8) '!' GenIni('.\Test3.ini', 100, 15) '!' GenIni('.\Test4.ini', 250, 18) '!' GenIni('.\Test5.ini', 500, 12) 'Done!' ? ? EndIf 'Press a key to continue ' Get $ ? dim $arrInifiles, $inifile, $tmpfile, $ini, $arrSections, $arrKeys, $section, $key, $d0, $d1, $d2, $Size, $Start, $Value $arrInifiles = ".\test1.ini", ".\test2.ini", ".\test3.ini", ".\test4.ini", ".\test5.ini" right( " size",8 ) right( " LTime",8 ) right( " time1",8 ) right( " time2",8 ) " filename" ? for each $inifile in $arrInifiles if $inifile $tmpfile = @scriptdir+"\"+@ScriptName+".tmp" COPY $inifile $tmpfile $size = GetFileSize($tmpfile) ;-- read with ini functions -- $start = @TICKS $ini = IniArray($tmpfile) $d0 = @TICKS - $Start $start = @TICKS $arrSections = Split(ReadIniArray(''), Chr(10)) for each $section in $arrSections $arrKeys = Split(ReadIniArray($section), Chr(10)) for each $key in $arrKeys $value = ReadIniArray($section, $key ) next next $d1 = @TICKS - $start ;-- read with ReadProfileString -- $start = @TICKS $arrSections = ReadProfileString( $tmpfile, "", "" ) $arrSections = split($arrSections, chr(10) ) for each $section in $arrSections if $section $arrKeys = ReadProfileString( $tmpfile, $section, "" ) $arrKeys = split($arrKeys, chr(10) ) for each $key in $arrKeys if $key $value = ReadProfileString( $tmpfile, $section, $key ) endif next endif next $d2 = @TICKS - $start right( " "+$size,8 ) right( " "+$d0,8 ) right( " "+$d1,8 ) right( " "+$d2,8 ) " " $inifile ? DEL $tmpfile endif next ? "finished" ? get $ Function GenIni($_File, $_S, $_MKey) Dim $_, $_X, $_Y Dim $_Section, $_Key, $_Data Del $_File SRnd(@MSECS) For $_X = 1 to $_S ; Sections For $_Y = 1 to 1 + Rnd($_MKey) ; Keys $_Section = 'SECTION_' + Right('0000' + $_X, 4) $_Key = 'Key_' + Right('00' + $_Y, 2) $_Data = Left('The quick brown fox jumped over the lazy dog.', Rnd(30)) $_ = WriteProfileString($_File, $_Section, $_Key, $_Data) Next If $_X Mod 50 = 0 '.' EndIf Next Exit 0 EndFunction ;; ;;====================================================================== ;; ;;FUNCTION IniArray() ;; ReadIniArray() - subfunction for reading array ;; WriteIniArray() - subfunction for writing array ;; ;;ACTION Reads INI file into an array; ;; Writes array to INI format file & reloads the array with fresh data ;; ReadIniArray() reads a Section:Key:Data set from the array ;; WriteIniArray() writes a Section:Key:Data set to the array ;; ;;AUTHOR Glenn Barnas ;; ;;VERSION 1.0 / 2011-08-02 ;; 1.1 / 2013-04-17 - improve file load on large files ;; 1.2 / 2013-04-20 - bugfixes: ;; Read empty section or file, write empty section, ;; WriteIniArray() - fix error if duplicate write of null data ;; Added Options to IniArray for Write to add blank ;; lines between sections ;; ;;SYNTAX IniArray(filename, Action [,options]) ;; ;;PARAMETERS filename - REQUIRED - the name of the INI file to read/write ;; ;; action - OPTIONAL - "R" or "W" for Read or Write - Defaults to Read ;; ;; options - OPTIONAL - bitwise settings to control output formatting ;; Value Mode Description ;; 1 Write Adds blank lines between Sections when true ;; 2 Write Suppress the array reload on write, useful when ;; no further INI manipulation is planned. ;; ;;REMARKS Reads an entire INI file into an array for processing. There is no limit ;; on the size of the INI file, other than any Kix limits on general file sizes. ;; On read, blank lines and comments (lines that begin with ";" or "#") are ;; ignored. On write, only sections that contain data/value pairs are written, ;; mimicing the action of WriteProfileString(). Similarly, only Keys that ;; have non-null values are written. ;; ;; The global array called $a_INIDATA_ is used for operations and will be declared ;; by the first call to IniArray if it was not previously defined. ;; ;; The secondary functions ReadIniArray() and WriteIniArray() make using the ;; IniArray() UDF as easy to use as ReadProfileString() and WriteProfileString(), ;; simply requiring calls to IniArray to load and then save the INI file. The ;; Read and Write sub-functions use the same syntax as the ProfileString ;; functions but reference the array instead of the INI file. ;; ;; NOTE: During array manipulation, deleted records are set to null and not reused. ;; Similarly, when a new key/data pair is added, deleted array items are ;; not reused. When the array is written to disk, the null records are skipped. ;; When the file is again read, the array will only contain valid data with no ;; empty records. ;; WRITING an array causes a RE-READ operation, cleaning the empty records. ;; ;; NOTE: IMPORTANT - When using WriteIniArray() to create a new Ini-array, you MUST ;; first declare the array via Dim $a_INIARRAY_[1, 0]. This is not necessary ;; if you read an INI file first with IniArray(filename). ;; ;;RETURNS IniArray: Returns 1 on success or 0 on failure ;; Populates a two-dimensional array of two-dimensional arrays. The first element ;; is the section name, the second element is a two-dimensional array consisting of ;; the key/data pairs. ;; ;; ReadIniArray: returns the data specified by the section/value pair ;; WriteIniArray: Returns 1 on success or 0 on failure ;; Updates the global array with the section/value/data provided ;; ;; Consider the following simple INI file: ;; [COLORS] ;; Apple=Red ;; Lime=Green ;; [TASTES] ;; Apple=sweet ;; Lime=sour ;; ;; The call $Rc = IniArray('inifile.ini') would populate the $a_INIDATA_ array ;; that contained the following values: ;; $a_INIDATA_[0,0] contains "COLORS" ;; $a_INIDATA_[1,0] contains an array of data/value pairs from this section ;; - extract with $aData = $a_INIDATA_[1,0] ;; $aData[0,0] contains "Apple"; $aData[1,0] contains "Red" ;; $aData[0,1] contains "Lime"; $aData[1,1] contains "Green" ;; ;; $a_INIDATA_[0,1] contains "TASTES" ;; $a_INIDATA_[1,1] contains Array of data/value pairs from this section ;; - extract with $aData = $a_INIDATA_[1,1] ;; $aData[0,0] contains "Apple"; $aData[1,0] contains "Sweet" ;; $aData[0,1] contains "Lime"; $aData[1,1] contains "Sour" ;; ;; ; the following would return "Sweet", just like ReadProfileString ;; $Taste = ReadIniArray('TASTES', 'Apple') ;; ;; ;;DEPENDENCIES none ;; ;;TESTED WITH W2K, WXP, W2K3, W2K8, W7 ;; ;;EXAMPLES INI FILE READ: ;; $Rc = IniArray('testfile.ini') ;; 'File contains ' UBound($a_INIDATA_) + 1 ' sections' ? ;; ;; ENUMERATE: ;; For $I = 0 to UBound($a_INIDATA_, 2) ;; $aData = $a_INIDATA_[1, $I] ;; ;; 'Section: ' $a_INIDATA_[0, $I] ' contains ' UBound($aData, 2) + 1 ' Data/Value elements' ? ;; ' Press a key: ' get $ ? ;; ;; For $J = 0 to UBound($aData, 2) ;; $J ': ' $aData[0, $J] ' = ' $aData[1, $J] ? ;; Next ;; Next ;; ;; ELEMENT READ: ;; ; Return a specific value ;; $Value = ReadIniArray('SectionName', 'keyname') ;; ; Return an array of all section names ;; $aSections = Split(ReadIniArray(''), Chr(10)) ;; ; Return an array of Keys in a specific section ;; $aKeys = Split(ReadIniArray('SectionName'), Chr(10)) ;; ;; ELEMENT WRITE/UPDATE: ;; ; Write a key/value pair in the named section ;; $Rc = WriteIniArray('SectionName', 'keyname', 'Data') ;; ;; ELEMENT DELETE: ;; ; Remove the named key from the defined section ;; $Rc = WriteIniArray('SectionName', 'keyname') ;; ;; SECTION DELETE: ;; ; Remove all key/value pairs from the section, deleting the section ;; $Rc = WriteIniArray('SectionName') ;; ;; INI FILE WRITE: ;; ; Flush the array to the file and then reload the array ;; $Rc = IniArray('testfile.ini', "W") ; Function IniArray($_fSrcFile, OPTIONAL $_DataWrite, OPTIONAL $_Options) Dim $_ ; temp var Dim $_Fp ; file pointer Dim $_C, $_I, $_J ; index pointers Dim $_AddLine, $_NoRead ; Option Vars Dim $_Sect ; Section Name Dim $_aDat ; Data pair array Dim $_aData[1, 49] ; Section & Data Arrays $_NoRead = 0 ; perform read after write If $_Options If $_Options & 1 $_AddLine = @CRLF EndIf If $_Options & 2 $_NoRead = 1 ; write & exit w/o re-reading EndIf EndIf ; Obtain a File Handle for Read or Write operations ; ============================================================ $_Fp = FreeFileHandle ; locate an available file handle If Not $_Fp Exit 1 ; none available - exit EndIf ; WRITE: verify that we have properly formatted data ; ============================================================ If $_DataWrite = 'W' ; Write operation If VarType($a_INIDATA_) < 8192 ; Not an array - exit! Exit 87 EndIf Del $_fSrcFile ; delete any pre-existing file $_ = Open($_Fp, $_fSrcFile, 5) ; open the file for write/create If @ERROR ReDim $a_INIDATA_[1, 0] ; create empty array Exit @ERROR ; exit if error opening EndIf ; Write the section data. If no data exists in the section, do not write anything! For $_I = 0 to UBound($a_INIDATA_, 2) $_Sect = $a_INIDATA_[0, $_I] ; Section name to write $_aData = $a_INIDATA_[1, $_I] ; Data array If UBound($_aData, 2) > -1 ; create Sect and write data if data is present $_ = '[' + $_Sect + ']' + @CRLF ; section name $_C = 0 For $_J = 0 to UBound($_aData, 2) ; key/data pairs If $_aData[1, $_J] ; only write keys that have non-null data $_C = 1 $_ = $_ + $_aData[0, $_J] + '=' + $_aData[1, $_J] + @CRLF EndIf Next If $_C $_ = WriteLine($_Fp, $_ + $_AddLine) ; write the output line EndIf If @ERROR $_ = Close($_Fp) ; close the file Exit @ERROR EndIf ; exit if error writing EndIf Next $_ = Close($_Fp) ; close the file ReDim $_aData[1, 0] ; clear array If $_NoRead exit 0 ; exit here without a re-read of the freshly written data EndIf EndIf ; declare the INI array if undefined If Not IsDeclared($a_INIDATA_) Global $a_INIDATA_[1,0] EndIf ; READ: Load the ini file into an array ; ============================================================ $_I = -1 $_J = -1 ; Initialize index pointers $_ = Open($_Fp, $_fSrcFile, 2) ; open the file for read If @ERROR ReDim $a_INIDATA_[0] ; return an empty array Exit @ERROR ; exit if error opening EndIf ReDim $a_INIDATA_[49], $_aKey[49], $_aData[49] ; Prep Section & Data Arrays for Read $_ = Trim(ReadLine($_Fp)) ; read INI, removing leading/trailing spaces While Not @ERROR ; loop through the file contents If Left($_, 1) = '[' ; found a section If $_I >= 0 ; process prior section data, if any If $_J >= 0 ReDim Preserve $_aData[$_J] ; trim data array to size ReDim Preserve $_aKey[$_J] ; trim data array to size dim $sps[1] $sps[0]=$_aKey $sps[1]=$_aData $a_INIDATA_[$_I][1] = $sps ReDim $_aKey[49] ; create the data array for the new section ReDim $_aData[49] ; create the data array for the new section $_J = -1 Else $_I = $_I - 1 ; ignore empty sections EndIf EndIf $_I = $_I + 1 ; increment the section index If $_I Mod 49 = 0 And $_I > 0 ReDim Preserve $a_INIDATA_[$_I + 50] ; increase the section input buffer EndIf dim $indx[1] $indx[0] = Split(SubStr($_, 2), ']')[0] $a_INIDATA_[$_I] = $indx Else If Not InStr(';#', Left($_, 1)) And Len($_) > 2 $_aDat = Split($_, '=') ; break into array $_J = $_J + 1 ; increment the data index If $_J Mod 49 = 0 And $_J > 0 ReDim Preserve $_aData[$_J + 50] ; increase the key input buffer EndIf $_aKey[$_J] = $_aDat[0] ; Store the value $_aData[$_J] = $_aDat[1] ; Store the value EndIf EndIf $_ = Trim(ReadLine($_Fp)) ; remove leading/trailing spaces Loop ; done with input data $_ = Close($_Fp) ; close the file $_ = 2 ; prep for Not Found exit ; process the last/only section If $_I >= 0 If $_J >= 0 ReDim Preserve $_aKey[$_J] ; trim data array to size ReDim Preserve $_aData[$_J] ; trim data array to size dim $sps[1] $sps[0]=$_aKey $sps[1]=$_aData $a_INIDATA_[$_I][1] = $sps Else ReDim Preserve $_aData[0] ReDim Preserve $_aKey[0] ; trim data array to size dim $sps[1] $sps[0]=$_aKey $sps[1]=$_aData $a_INIDATA_[$_I][1] = $sps EndIf ReDim Preserve $a_INIDATA_[$_I] ; trim section array to size $_ = 0 EndIf Exit $_ ; exit success EndFunction Function ReadIniArray(OPTIONAL $_Section, OPTIONAL $_Key) Dim $_I, $_J ; Index pointers Dim $_aData, $_aSrc ; Array of Key/Data pairs Dim $_Cmd Dim $_S Dim $_ ; exit immediately if the data format is invalid If VarType($a_INIDATA_) < 8192 ; data but not an array - exit! Exit 87 EndIf ; Get the array size $_S = UBound($a_INIDATA_) If $_S <= 0 Exit 87 EndIf Dim $_aSectIdx[$_S] ; Section Index Array ; Create a section index array For $_I = 0 to $_S $_aSectIdx[$_I] = $a_INIDATA_[$_I][0] Next ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] If Not $_Section $ReadIniArray = Join($_aSectIdx, Chr(10)) Exit 0 EndIf ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) If $_I < 0 Exit 2 EndIf ; section not found - Exit $_aData = $a_INIDATA_[$_I][1] ; Extract the key/value array ; Create a Key index for the requested section If Not $_Key $readiniarray=join($_aData[0],chr(10)) exit 0 endif if 0>ascan($_aData[0],$_Key) exit 2 endif $ReadIniArray = $_aData[1][ascan($_aData[0],$_Key)] EndFunction Function WriteIniArray($_Section, OPTIONAL $_Key, OPTIONAL $_Data) ; exit immediately if the data format is invalid If VarType($a_INIDATA_) < 8192 ; data but not an array - exit! Exit 87 EndIf Dim $_aSectIdx[UBound($a_INIDATA_, 2)] ; Section Index Array Dim $_I, $_J ; Index pointers Dim $_aData ; Key/Data array ; Create a section index array For $_I = 0 to UBound($a_INIDATA_, 2) $_aSectIdx[$_I] = $a_INIDATA_[0, $_I] Next ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) ; If the section does not exist and Keydata is present - add the section and key/value pair (new Sect:Key:Data) If $_I < 0 ; section not found - Add if Data is present If $_Key And $_Data $_I = 0 ; default to empty array If $a_INIDATA_[0, 0] $_I = UBound($a_INIDATA_, 2) + 1 ; find next section index EndIf ReDim Preserve $a_INIDATA_[1, $_I] ; Add new section ReDim $_aData[1] ; create data array $_aData[0] = $_Key ; key $_aData[1] = $_Data ; data $a_INIDATA_[0, $_I] = $_Section ; section name $a_INIDATA_[1, $_I] = $_aData ; section data Else Exit 0 ; nothing to do EndIf Else ; the section does exist, locate the Key ; If the Key is null, delete the section (delete Section) If Not $_Key ReDim $_aData[1] ; create empty keys array $a_INIDATA_[1, $_I] = $_aData ; write to section $a_INIDATA_[0, $_I] = '' ; write null section name Else $_aData = $a_INIDATA_[1, $_I] ; Extract the key/value array ; Create a Key index for the requested section Dim $_aKeyIdx[UBound($_aData)] For $_J = 0 to UBound($_aData) $_aKeyIdx[$_J] = $_aData[$_J][0] ; create the index Next $_J = aScan($_aKeyIdx, $_Key) ; find the key ; If the key does not exist, add the key/data (new Key/data) If $_J < 0 If $_Data $_aData = $a_INIDATA_[1, $_I] ; array of key/data pairs $_J = UBound($_aData) + 1 ; find next key index ReDim Preserve $_aData[$_J] ; create data array dim $dats[1] $dats[0] = $_Key ; key $dats[1] = $_Data ; data $_aData[$_J] = $dats $a_INIDATA_[1, $_I] = $_aData ; section data Else Exit 0 ; nothing to do (no data) EndIf Else ; the key exists - either Update or Delete ; if the data is not null, write the new data (update key) If $_Data $_aData[$_J][1] = $_Data ; If the data is null, write empty key and data values (delete key) Else dim $dats[1] $_aData[$_J] = $dats ; delete key EndIf $a_INIDATA_[1, $_I] = $_aData ; update the array EndIf EndIf EndIf Exit 0 EndFunction |
||||||||
|
|
|||||||
modified my readiniarray to just be a stub: Code: Function ReadIniArray($file, OPTIONAL $_Section, OPTIONAL $_Key) $ReadIniArray = readprofilestring($file,$_Section,$_Key) EndFunction and still the processing time hikes in the last one: Code: 91539 312 5523 2074 .\test5.ini |
||||||||
|
|
|||||||
Hello, done a new version of functions with a handle instead of an array (and prefix all functions with CM). the script tests files with 3 methods : - time1 uses ReadProfileString - time2 uses my own functions CMIniArray, CMReadIniArray, etc... - time3 uses IniArray, ReadIniArray, etc... here is the complete code for test (just create a subdir called "test" with inifiles or change the content of $arrInifiles) : Code: break on $=setoption( "explicit", "on" ) dim $arrInifiles, $inifile, $tmpfile, $size, $ini, $start, $d1, $d2, $d3 dim $arrSections, $arrKeys, $section, $key, $value $arrInifiles = @scriptdir+"\test\file010.ini", @scriptdir+"\test\file020.ini", @scriptdir+"\test\file030.ini", @scriptdir+"\test\file040.ini", @scriptdir+"\test\file050.ini", @scriptdir+"\test\file060.ini", @scriptdir+"\test\file070.ini", @scriptdir+"\test\file080.ini", @scriptdir+"\test\file090.ini", @scriptdir+"\test\file100.ini", "" right( " size", 8 ) right( " time1",8 ) right( " time2",8 ) right( " time3",8 ) " filename" ? for each $inifile in $arrInifiles if $inifile $tmpfile = @scriptdir+"\"+@ScriptName+".tmp" $size = GetFileSize($inifile) ;-- read with ReadProfileString -- $start = @TICKS COPY $inifile $tmpfile $arrSections = ReadProfileString( $tmpfile, "", "" ) $arrSections = split($arrSections, chr(10) ) for each $section in $arrSections if $section $arrKeys = ReadProfileString( $tmpfile, $section, "" ) $arrKeys = split($arrKeys, chr(10) ) for each $key in $arrKeys if $key $value = ReadProfileString( $tmpfile, $section, $key ) endif next endif next $d1 = @TICKS - $start ;-- read with ini functions (version Christophe) -- $start = @TICKS $ini = CMIniArray( $inifile ) if not @error $arrSections = CMINISections($ini) for each $section in $arrSections $arrKeys = CMINIKeys( $ini, $section ) for each $key in $arrKeys $value = CMReadIniArray( $ini, $section, $key ) next next endif $ = CMCloseIniArrayHandle( $ini ) $d2 = @TICKS - $start $ = CMCloseIniArrayHandle( $ini ) ;-- read with ini functions (version Glenn) -- $start = @TICKS $ini = IniArray( $inifile ) if not @error $arrSections = INISections($ini) for each $section in $arrSections $arrKeys = INIKeys( $ini, $section ) for each $key in $arrKeys $value = ReadIniArray( $ini, $section, $key ) next next endif $d3 = @TICKS - $start right( " "+$size,8 ) right( " "+$d1, 8 ) right( " "+$d2, 8 ) right( " "+$d3, 8 ) " " $inifile ? DEL $tmpfile endif next ;------------------------------------------------------------------------------- ; set of function to read in a ini file (version Christophe) ;------------------------------------------------------------------------------- ; internal function ; returned handle is the index of the entry in a global array ; each item in the array is an array of 4 values : ; entry 0 is a boolean (handle used or not) ; entry 1 is the name of the ini file ; entry 2 is the number of sections found in the ini file ; entry 3 is an array of sections structures ; ; each section structure is an array of 3 values : ; entry 0 is the name of the section ; entry 1 is the number of keys in the section ; entry 2 is an array of keys structures ; ; each key structure is an array of 2 values : ; entry 0 is the name of the key ; entry 1 is the value ;------------------------------------------------------------------------------- function CMGetIniArrayHandle( $inifilename ) if not IsDeclared( $_IniArrayGlobalHandle ) ;-- initialize global internal variables -- global $_IniArrayGlobalHandleNb $_IniArrayGlobalHandleNb = -1 global $_IniArrayGlobalHandleInc $_IniArrayGlobalHandleInc = 32 global $_IniArrayGlobalHandle[$_IniArrayGlobalHandleInc] endif dim $i for $i = 0 to $_IniArrayGlobalHandleNb if ($_IniArrayGlobalHandle[$i][0]=1) and ($_IniArrayGlobalHandle[$i][1]=$inifilename) ;-- inifile already loaded : reuse the same handle -- $CMGetIniArrayHandle = $i exit 0 endif if ($_IniArrayGlobalHandle[$i][0]=0) ;-- use a free existing handle -- $_IniArrayGlobalHandle[$i] = 1, $inifilename, 0, "" $CMGetIniArrayHandle = $i exit 0 endif next ;-- use a new handle -- $_IniArrayGlobalHandleNb = $_IniArrayGlobalHandleNb + 1 if $_IniArrayGlobalHandleNb > UBound($_IniArrayGlobalHandle) redim preserve $_IniArrayGlobalHandle[ UBound($_IniArrayGlobalHandle)+$_IniArrayGlobalHandleInc ] endif $_IniArrayGlobalHandle[$_IniArrayGlobalHandleNb] = 1, $inifilename, 0, "" $CMGetIniArrayHandle = $_IniArrayGlobalHandleNb endfunction ;------------------------------------------------------------------------------- ; external function ;------------------------------------------------------------------------------- function CMCloseIniArrayHandle( $inihandle ) if $inihandle < 0 exit endif if $inihandle > $_IniArrayGlobalHandleNb exit endif if $_IniArrayGlobalHandle[$inihandle][0]=1 if $_IniArrayGlobalHandle[$inihandle][2]>0 dim $i for $i = 0 to UBound( $_IniArrayGlobalHandle[$inihandle][3] ) $_IniArrayGlobalHandle[$inihandle][3][$i][1] = 0 $_IniArrayGlobalHandle[$inihandle][3][$i][2] = "" next endif $_IniArrayGlobalHandle[$inihandle] = 0, "", 0, "" endif endfunction function CMIniArray( $inifilename ) dim $fHandle ; file pointer $CMIniArray = -1 $fHandle = FreeFileHandle() ; locate an available file handle if not $fHandle exit 1 ; none available - exit endif ;-- try to open file for read into a memory structure -- if Open($fHandle, $inifilename, 2)<>0 ; open the file for read exit 2 ; error opening - exit endif dim $inihandle $inihandle = CMGetIniArrayHandle( $inifilename ) dim $inc $inc = 128 dim $arrSections[$inc], $nbSection, $section dim $arrKeys[$inc], $nbKey dim $_, $ch, $i $nbSection = -1 do ; loop through the file contents $_ = Trim(ReadLine($fHandle)) ; remove leading/trailing spaces if $_ $ch = Left($_, 1) select case $ch="[" $ch = Right($_, 1) if $ch="]" ; found a section If $nbSection > -1 ; process prior section data, if any gosub _CMIniArray_SavePreviousSection endif $nbKey = -1 $nbSection = $nbSection + 1 ; increment the section index if $nbSection > UBound($arrSections) redim Preserve $arrSections[ UBound($arrSections)+$inc ] endif $section = SubStr($_, 2, len($_)-2 ) endif case $ch=";" ; comment line case $ch="#" ; comment line case Len($_) > 2 if $nbSection > -1 $nbKey = $nbKey + 1 if $nbKey > UBound($arrKeys) redim preserve $arrKeys[ UBound($arrKeys)+$inc ] endif $i = instr( $_, "=" ) if $i $arrKeys[ $nbKey ] = substr( $_, 1, $i-1), substr( $_, $i+1 ) else $arrKeys[ $nbKey ] = $_, "" endif endif case 1 endselect endif Until @ERROR ; done with input data $_ = Close($fHandle) ; close the file ;-- process the last/only section -- If $nbSection > -1 gosub _CMIniArray_SavePreviousSection redim preserve $arrSections[$nbSection] $_IniArrayGlobalHandle[$inihandle][2] = $nbsection + 1 $_IniArrayGlobalHandle[$inihandle][3] = $arrSections else $_IniArrayGlobalHandle[$inihandle][2] = 0 $_IniArrayGlobalHandle[$inihandle][3] = "" endif $CMIniArray = $inihandle ; return the array Exit 0 ; exit success :_CMIniArray_SavePreviousSection if $nbKey=-1 $arrSections[$nbSection] = $section, 0, "" else redim preserve $arrKeys[ $nbKey ] $arrSections[$nbSection] = $section, ($nbKey+1), $arrKeys endif return endfunction function CMReadIniArray( $inihandle, OPTIONAL $Section, OPTIONAL $Key ) ;-- in the code -- ; $_IniArrayGlobalHandle[$inihandle][2] number of sections ; $_IniArrayGlobalHandle[$inihandle][3] array of sections ; $_IniArrayGlobalHandle[$inihandle][3][$sectionindex][1] number of keys in a section ; $_IniArrayGlobalHandle[$inihandle][3][$sectionindex][2] array of (key, value) in a section ; ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] $CMReadIniArray = "" If Not $Section if $_IniArrayGlobalHandle[$inihandle][2] > 0 ;-- enum section name in the inifile -- for each $section in $_IniArrayGlobalHandle[$inihandle][3] $CMReadIniArray = $CMReadIniArray + $section[0] + chr(10) next endif Exit 0 endif ; Search the index for a section dim $sectionindex, $i, $item $sectionindex = -1 if $_IniArrayGlobalHandle[$inihandle][2] > 0 for $i = 0 to UBound($_IniArrayGlobalHandle[$inihandle][3]) if $_IniArrayGlobalHandle[$inihandle][3][$i][0]=$section $sectionindex = $i endif next endif If $sectionindex < 0 Exit 2 endif ; section not found - Exit If Not $Key ;-- enum key name in the section -- if $_IniArrayGlobalHandle[$inihandle][3][$sectionindex][1]<>0 for each $item in $_IniArrayGlobalHandle[$inihandle][3][$sectionindex][2] $CMReadIniArray = $CMReadIniArray + $item[0] + chr(10) next endif Exit 0 endif for each $item in $_IniArrayGlobalHandle[$inihandle][3][$sectionindex][2] if $item[0]=$key $CMReadIniArray = $item[1] exit 0 ; key found endif next exit 2 ; key not found endfunction function CMINISections( $inihandle ) $CMINISections = CMReadIniArray( $inihandle ) if Len($CMINISections) > 0 $CMINISections=split($CMINISections,chr(10)) endif endfunction function CMINIKeys( $inihandle, $section ) $CMINIKeys = CMReadIniArray( $inihandle, $section ) if Len($CMINIKeys) > 0 $CMINIKeys=split($CMINIKeys,chr(10)) endif endfunction ;------------------------------------------------------------------------------- ; set of function to read in a ini file (version Glenn) ;------------------------------------------------------------------------------- ;FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=202790#Post202790 Function IniArray($_fSrcFile, OPTIONAL $_aDataWrite) Dim $_ ; temp var Dim $_Fp ; file pointer Dim $_I, $_J ; index pointers Dim $_Sect ; Section Name Dim $_aDat ; Data pair Dim $_aSect[1,0], $_aData[1, 0] ; Section & Data Arrays ; Obtain a File Handle for Read or Write operations ; ============================================================ $_Fp = FreeFileHandle ; locate an available file handle If Not $_Fp Exit 1 ; none available - exit EndIf ; WRITE: verify that we have properly formatted data ; ============================================================ If VarType($_aDataWrite) > 0 ; Write the array to the INI file and exit If VarType($_aDataWrite) < 8192 ; data but not an array - exit! Exit 87 EndIf Del $_fSrcFile ; delete any pre-existing file $_ = Open($_Fp, $_fSrcFile, 5) ; open the file for write/create If @ERROR Exit @ERROR EndIf ; exit if error opening ; Write the section data. If no data exists in the section, do not write anything! For $_I = 0 to UBound($_aDataWrite, 2) $_Sect = $_aDataWrite[0, $_I] ; Section name to write $_aData = $_aDataWrite[1, $_I] ; Data array If UBound($_aData, 2) > -1 ; create Sect and write data if data is present $_ = '[' + $_Sect + ']' + @CRLF ; section name For $_J = 0 to UBound($_aData, 2) ; key/data pairs If $_aData[1, $_J] ; only write keys that have non-null data $_ = $_ + $_aData[0, $_J] + '=' + $_aData[1, $_J] + @CRLF EndIf Next $_ = WriteLine($_Fp, $_) ; write the output line If @ERROR Exit @ERROR EndIf ; exit if error writing EndIf Next $_ = Close($_Fp) ; close the file ReDim $_aData[1, 0] ; clear array ; exit 0 ; do not exit here to force a re-read of the freshly written data EndIf ; READ: Load the ini file into an array ; ============================================================ $_I = -1 $_J = -1 ; Initialize index pointers $_ = Open($_Fp, $_fSrcFile, 2) ; open the file for read If @ERROR Exit @ERROR EndIf ; exit if error opening Do ; loop through the file contents $_ = Trim(ReadLine($_Fp)) ; remove leading/trailing spaces If Left($_, 1) = '[' ; found a section If $_I >= 0 ; process prior section data, if any $_aSect[1, $_I] = $_aData ReDim $_aData[1, 0] $_J = -1 EndIf $_I = $_I + 1 ; increment the section index ReDim Preserve $_aSect[1, $_I] $_aSect[0, $_I] = Split(SubStr($_, 2), ']')[0] Else If Not Left($_, 1) = ';' And Not Left($_, 1) = '#' And Len($_) > 2 $_aDat = Split($_, '=') ; break into array $_J = $_J + 1 ; increment the data index ReDim Preserve $_aData[1, $_J] $_aData[0, $_J] = $_aDat[0] ; Store the data $_aData[1, $_J] = $_aDat[1] ; Store the data EndIf EndIf Until @ERROR ; done with input data $_ = Close($_Fp) ; close the file ; process the last/only section If $_I >= 0 $_aSect[1, $_I] = $_aData EndIf $IniArray = $_aSect ; return the array Exit 0 ; exit success EndFunction Function ReadIniArray($_aIniFile, OPTIONAL $_Section, OPTIONAL $_Key) ; exit immediately if the data format is invalid If VarType($_aIniFile) < 8192 ; data but not an array - exit! Exit 87 EndIf Dim $_aSectIdx[UBound($_aIniFile, 2)] ; Section Index Array Dim $_I, $_J ; Index pointers Dim $_aData ; Array of Key/Data pairs ; Create a section index array For $_I = 0 to UBound($_aIniFile, 2) $_aSectIdx[$_I] = $_aIniFile[0, $_I] Next ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] If Not $_Section $ReadIniArray = Join($_aSectIdx, Chr(10)) Exit 0 EndIf ; Search the index for a section $_I = aScan($_aSectIdx, $_Section) If $_I < 0 Exit 2 EndIf ; section not found - Exit $_aData = $_aIniFile[1, $_I] ; Extract the key/value array ; Create a Key index for the requested section Dim $_aKeyIdx[UBound($_aData, 2)] For $_J = 0 to UBound($_aData, 2) $_aKeyIdx[$_J] = $_aData[0, $_J] Next ; If the Key is null, return a delimited string of Keys [same as ReadProfileString(file, section)] If Not $_Key $ReadIniArray = Join($_aKeyIdx, Chr(10)) Exit 0 EndIf ; Search the index for a Key $_J = aScan($_aKeyIdx, $_Key) If $_J < 0 Exit 2 EndIf ; Key not found $ReadIniArray = $_aData[1, $_J] Exit 0 EndFunction ;Modified FROM http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=203126#Post203126 function INISections($array) $INISections = readiniarray($array) if LEN($INISections) > 0 $INISections=split($INISections,chr(10)) endif endfunction function INIKeys($array,$section) $INIKeys = readiniarray($array,$section) if LEN($INIKeys) > 0 $INIKeys=split($INIKeys,chr(10)) endif endfunction On a Intel E5200 (Dual-Core 2.5Ghz) with 2Gb, the results are : Code: size time1 time2 time3 filename 91 15 0 0 .\test\file010.ini 2019 0 16 0 .\test\file020.ini 7553 16 47 46 .\test\file030.ini 15173 94 1110 2484 .\test\file040.ini 28149 187 1704 2296 .\test\file050.ini 34527 47 250 500 .\test\file060.ini 43179 94 1031 1938 .\test\file070.ini 69058 125 515 1125 .\test\file080.ini 103587 235 797 1922 .\test\file090.ini 138116 375 1062 2969 .\test\file100.ini On a Intel Pentium 4 (Mono-Core 2.4Ghz) with 1Gb, the results are : Code: size time1 time2 time3 filename 91 0 0 0 .\test\file010.ini 2019 15 16 16 .\test\file020.ini 7553 31 94 93 .\test\file030.ini 15173 203 2563 6094 .\test\file040.ini 28149 468 3625 6250 .\test\file050.ini 34527 125 610 1328 .\test\file060.ini 43179 218 2532 5015 .\test\file070.ini 69058 360 1281 3281 .\test\file080.ini 103587 781 1938 5969 .\test\file090.ini 138116 1328 2562 9641 .\test\file100.ini ReadProfileString is always the faster with local file !!! I use a global array to allocate handle (with associated data). redim is automatic if many differents ini files are used. I have not tested all cases for CMReadIniArray and I suppress the optional parameter for write in CMIniArray. Code is not very easy to read because of use of array of array of array ... but it seems to be fast. Not sure i'll use it in my login script because all procedures are already written to copy inifile locally before using ReadProfileString and delete temp file when finished. But it was a good exercize for fun |
||||||||
|
|
|||||||
From the testing, there seems to be a threshold where performance drops off dramatically. My login.ini is just under 12K. When I enabled process timestamps, the script completed 600ms faster using IniArray. Most config files are in that 8-12K size, but when I added a few records to the test config, I lost the 600ms and another 2 seconds! I've rolled back to the cached INI file model so performance is consistent. Thanks for your ideas! Glenn |