Archive: Text Replace - a little different


Text Replace - a little different
  I'm creating an installer for some new deployment notebook PC's that will not be connected to an external network (they'll never see 'the internet'). They have some apps that display data via HTML that don't really render well in IE (surprise surprise), so I am installing Firefox. I'm attempting to write the installer so that it is silent, but I'm losing some configurations. I'm also trying to make it so that the settings are not dependent on the version of Firefox which is installed so I do not want to crate a specific profile and copy it over, instead I would rather search for specific strings and replace as they appear.

I'm also not a 'code guy' so I tend to approach the actual scripting with a 'brute force' way of doing things. I'd appreciate any pointers on a more elegant/efficient script.

I'm using the MUI, scripting by hand as I am still learning the process. I'm attempting to implement these changes in the Firefox profile, but instead of using a batch script I'd like it all to be handled in the "Firefox" section of my overall installer.

Essentially, I'd like to point to the newly installed firefox.js file, search for the following strings, and change the boolean state (true/false) as appropriate for the installation
(default for these for strings is true, I want the script to find them if they exist and change them to false):

pref("browser.shell.checkDefaultBrowser", false)
pref("app.update.enabled", false);
pref("extensions.update.enabled", false);
pref("browser.search.update", false);


I'd like to write the script so that I can use an ini file as an input because I can reuse the same concept for editing a bunch of .cfg and .xml files in another installation that takes place later in my script.

My brute-force attempt (one of many) follows. I've redacted much of my working script, but left some of the header stuff for scrutiny/improvement suggestions.


Name "FirefoxSilent"
OutFile "FirefoxSilent.exe"

; Includes --------------------------
!include "MUI.nsh"
!include "TextReplace.nsh"
!include "StrRep.nsh"
!include "ReplaceInFile.nsh"

; Defines ---------------------------
!define FIREFOX "Firefox 3.6.3\Firefox Setup 3.6.3\*.*"


; MUI Settings ----------------------

!define MUI_ABORTWARNING
!define MUI_ICON "${INSTALL_ICON}"

; Welcome page
!define MUI_WELCOMEPAGE_TITLE '${WELCOME_TITLE}'
!define MUI_WELCOMEPAGE_TITLE_3LINES
!insertmacro MUI_PAGE_WELCOME
; Components page
!insertmacro MUI_PAGE_COMPONENTS
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!define MUI_FINISHPAGE_TITLE '${FINISH_TITLE}'
!define MUI_FINISHPAGE_TITLE_3LINES
!insertmacro MUI_PAGE_FINISH

; Language files
!insertmacro MUI_LANGUAGE "English"

; Reserve files
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS

; MUI end --------------------------

; Compression ----------------------
SetCompressor lzma


Section "FireFox" SEC01
SectionIn RO ;make section mandatory for installation

; Installation settings for a silent install of Firefox
WriteINIStr $TEMP\ffconfig.ini Install CloseAppNoPrompt true
WriteINIStr $TEMP\ffconfig.ini Install InstallDirectoryName "Mozilla Firefox"
WriteINIStr $TEMP\ffconfig.ini Install QuickLaunchShortcut false
WriteINIStr $TEMP\ffconfig.ini Install DesktopShortcut false
WriteINIStr $TEMP\ffconfig.ini Install StartMenuShortcuts true

; Use 7zip to unpack the installation files from the Firefox Setup executable from
; http://www.mozilla.com/en-US/firefox/personal.html
; Unpack to a directory with the same name as the downloaded file, then include that
; directory in the File /r statement below (/r means recursive)
File /r "${FIREFOX}"
ExecWait '"$INSTDIR\setup.exe" /INI=$TEMP\ffconfig.ini'

; Disable bookmark import wizard
WriteINIStr "C:\Program Files\Mozilla Firefox\override.ini" XRE EnableProfileMigrator false

; Don't make Firefox check to be default browser
/* THIS IS THE PART THAT DOESN'T WORK */
!insertmacro ReplaceInFile "C:\Program Files\Mozilla Firefox\defaults\pref\firefox.js" 'pref("browser.shell.checkDefaultBrowser", true);' 'pref("browser.shell.checkDefaultBrowser", false);'


; Cleanup
Delete $TEMP\ffconfig.ini ;after install
SectionEnd

I can't get the last piece to work, and I've tried multiple approaches to it, none of which will compile.

I've tried using the following (and others) but none really works as intended in my application:

http://nsis.sourceforge.net/ReplaceInFile
http://nsis.sourceforge.net/TextReplace_plugin

If you're searching for a whole line you just need to read the old file line by line writing each to a new file. When you come across the line you want to change you write the amended line instead. When done you replace the old file with the new. You will want to use TrimNewLines in there too before doing the comparison.

Stu


Thanks for the input, but I don't want to limit myself to just one line.

For the Firefox unattended install it would work fine, but not for the rest of my install package usage. I'd like to use the same method for both.

What's coming next is that I have a software package that I've installed, but it has a LOT of configuration files. Many of the configuration files are dependent on on input from the user to name the machine. These config files take a great deal of time to set up manually, and are easily prone to error if done incorrectly.

My thoughts on proceeding go like this this:

1. In my installer, I need to install the base software package and a patch, as well as adobe, firefox, and a couple other utilities. I've got all this working just fine.

2. Create a default configuration. When configuring the software, I'll enter a string such as ZZREPLACEMEZZ as the name I'll need to enter when prompted. This name will be filtered down through numerous cfg, xml, ini, and bat files throughout the profile. (actually this step is already complete).

3. I then have the installer copy over all the default profile installation files (created in step 2) and paste them into their respective directories. I have this step working also.

4. Lastly, I'll prompt the user for the 'Machine Name' during the installation process, and use that string (e.g. 'Machine1') as the string that will overwrite every instance of 'ZZREPLACEMEZZ' in all of the configuration files in the profile. This is the part I'm having trouble with.

I figure if I can get it to work in the Firefox profile stuff, I should be able to get it all working the same way for my other software profile. If I could do a blanket string search/replace for the root of the profile, and point to *.xml, *.ini, *.cfg, and *.bat as the files to perform the action instead of searching and replacing each file individually, that would be even better.

Any pointers to this end would be greatly appreciated.


Okay, I'm making a bit of progress. I'm using a model based on the TextReplace plugin found here.

Name "TextReplaceTest"

>OutFile "TextReplaceTest_.exe"

>!include "TextReplace.nsh"
>!include "FileFunc.nsh"
>!include "TextFunc.nsh"
>!include "WordFunc.nsh"

>Var INPUT_STR1 ;'pref("browser.shell.checkDefaultBrowser", true);'
>Var OUTPUT_STR1 ;'pref("browser.shell.checkDefaultBrowser", false);'

>;Var FIREFOX_JS ; FIREFOX_JS "C:\Program Files\Mozilla Firefox\defaults\pref\firefox.js"

>Page components
Page instfiles

Section"firefox.js" Firefox
ReadINIStr $INPUT_STR1 C
:test.ini Firefox_js In1
ReadINIStr $OUTPUT_STR1 C:test.ini Firefox_js Out1

; from http://nsis.sourceforge.net/Docs/AppendixE.html#E.1.2
${Locate} "C:\Program Files" "/L=F /M=firefox.js" "FindFile"
IfErrors 0 +2
MessageBox MB_OK"Error" IDOK +5

${textreplace::FindInFile} "$R0" "$INPUT_STR1" "/S=1" $0
MessageBox MB_OK "textreplace::FindInFile$\n$$0={$0}"

${textreplace::ReplaceInFile} "$R0" "$R0" "$INPUT_STR1" "$OUTPUT_STR1" "/S=1 /C=1 /AO=1" $0
MessageBox MB_OK "textreplace::ReplaceInFile$\n$$0={$0}"

${textreplace::Unload}
>SectionEnd


>Function FindFile
StrCpy $R0 $R9; $R0="%path%\filename.ext"
StrCpy $0 StopLocate
Push$0
FunctionEnd
>
My ini file is currently:
[Firefox_js]
In1='pref("browser.shell.checkDefaultBrowser", true);'
Out1='pref("browser.shell.checkDefaultBrowser", false);'

What I'd like to do is get the script to keep searching the ini file until there are no more entries in the section. Is this possible, or do I need to code it explicitly?

Okay, I'm still noodling this out. As I mentioned before, I'm not a programmer.

What if I do my above script a bit differently? For example, what if I do something like this:


Section "firefox.js" Firefox
; Find the firefox.js file
${Locate} "C:\Program Files" "/L=F /M=firefox.js" "FindFile"

loop:
Call GetReplaceText
Return

${textreplace::FindInFile} "$R0" "$INPUT_STR" "/S=1" $0
MessageBox MB_OK "textreplace::FindInFile$\n$$0={$0}"

${textreplace::ReplaceInFile} "$R0" "$R0" "$INPUT_STR" "$OUTPUT_STR" "/S=1 /C=1 /AO=1" $0
MessageBox MB_OK "textreplace::ReplaceInFile$\n$$0={$0}"

${textreplace::Unload}
done ;exit the loop
SectionEnd

Function GetReplaceText
; Open a configuration file
; Look at counter. If counter=0, start at the top of the file
; Read two lines
; Set the first line as $INPUT_STR
; Set the second line as $OUTPUT_STR
; Increment a counter to remember my location for the next function call
; If line is blank or end of file, set an error to jump out of the loop
FunctionEnd


Function FindFile
StrCpy $R0 $R9 ; $R0="%path%\filename.ext"
StrCpy $0 StopLocate
Push $0
FunctionEnd
>
At the moment, the configuration file would look like this:

pref("browser.shell.checkDefaultBrowser", true);
pref("browser.shell.checkDefaultBrowser", false);
pref("app.update.enabled", true);
pref("app.update.enabled", false);
pref("extensions.update.enabled", true);
pref("extensions.update.enabled", false);
pref("browser.search.update", true);
pref("browser.search.update", false);
Now I just have to figure out how to make this logic flow work. Anyone have any input?

I got it to work, but it's messy. Any code optimization would be appreciated.

The input search/replace file is called ffconfig.txt, and would be formatted as in my post above.


Name "LineReplace"
OutFile "LineReplace.exe"
InstallDir "$DESKTOP"

!include "FileFunc.nsh"
!include "TextFunc.nsh"
!include "WordFunc.nsh"

!define FFConfig "C:\ffconfig.txt"

; FIREFOX_JS "C:\Program Files\Mozilla Firefox\defaults\pref\firefox.js" (default location)

Var FIREFOX_JS

Section SEC01
; from http://nsis.sourceforge.net/Docs/AppendixE.html#E.1.2
${Locate} "C:\Program Files" "/L=F /M=firefox.js" "Lookie"
IfErrors 0 +2
MessageBox MB_OK "firefox.js not found" IDOK +2
StrCpy $FIREFOX_JS $R0
MessageBox MB_OK 'Firefox configuration file is located at: "$FIREFOX_JS"'
SetOutPath $INSTDIR
File "${FFConfig}"
Call ReadConfig
Delete "$INSTDIR\ffconfig.txt"
SectionEnd

Function ReadConfig
;$0 - config file
;$1 - current line
;$2 - next line
ClearErrors
FileOpen $0 "$DESKTOP\ffconfig.txt" r
IfErrors error
readtwolines:
FileRead $0 $1 ; read line from target file
IfErrors close ; check to see if end of line
FileRead $0 $2 ; read next line from target file
${TrimNewLines} $1 $3 ; clear rn for current line
${TrimNewLines} $2 $4 ; clear rn for next line
StrCpy $1 $3
StrCpy $2 $4
MessageBox MB_OK '$$1: $1$\r$\n$$2: $2$\r$\n'
Call Replaceline
Goto readtwolines

close:
FileClose $0
goto end

error:
SetErrors
StrCpy $0 ''

end:
FunctionEnd

Function Replaceline
; from: http://nsis.sourceforge.net/Replacing_Lines_in_a_Text_File
;$3 - handle to firefox.js file
;$R1 - temp file
;$4 - handle to temp file
;$5 - text string to change from config file
ClearErrors
FileOpen $3 "$FIREFOX_JS" "r" ; open target file for reading
GetTempFileName $R1 ; get new temp file name
FileOpen $4 $R1 "w" ; open temp file for writing
MessageBox MB_OK '$$1: $1$\r$\n$$2: $2$\r$\n$$R0: $R0$\r$\n$$R1: $R1'
loop:
FileRead $3 $5 ; read line from target file
IfErrors done ; check if end of file reached
StrCmp $5 "$1$\r$\n" 0 +2 ; compare line with search string with CR/LF
StrCpy $5 "$2$\r$\n" ; change line
StrCmp $5 "$1" 0 +2 ; compare line with search string without CR/LF (at the end of the file)
StrCpy $5 "$2" ; change line
FileWrite $4 $5 ; write changed or unchanged line to temp file
Goto loop

done:
FileClose $3 ; close target file
FileClose $4 ; close temp file
Delete "$FIREFOX_JS" ; delete target file
CopyFiles /SILENT $R1 "$FIREFOX_JS" ; copy temp file to target file
Delete $R1 ; delete temp file
FunctionEnd

Function Lookie
StrCpy $R0 $R9
; $R0="%path%\firefox.js"
StrCpy $0 StopLocate
Push $0
FunctionEnd
>