NSIS: reading DWORD from the registry? - nsis

I'm trying to update an existing installer to read a registry value (if it exists) and change the install settings to match existing installation settings when doing upgrade installations.
But I'm getting hung up on not being able to sucessfully read the registry value.
I write the key like this in the installer:
WriteRegDWORD HKLM "Software\${PRODUCT}" "IniPath" 0x00000026
This part works just fine, the key is created or modified, and is of type DWORD, all is well.
But then if I try to read that value back, I seem to be getting an empty value every time.
ReadRegDWORD $IniPath HKCU "Software\${PRODUCT}" "IniPath"
MessageBox MB_OK "$$IniPath=$IniPath"
Results in printing $IniPath=
I also tried doing ClearErrors before reading and IfErrors after, and it does appear to be getting into IfErrors...but I don't understand why it would be having errors and not seeing the key. I am running 32 bit windows, so I doubt this is 64bit "wrong section of the registry" error.
The line to write the DWORD is working just fine. If I delete the registry key, and examine the registry using regedit immediately after, the key is there. But regardless of whether the key existed.
So what could be going wrong to cause it to fail to read the value, or how to debug this?

You are writing to HKLM and reading from HKCU...
To debug registry issues you can use Process Monitor.

Related

how to move files on reboot in nsis

i need to move files on reboot from source to destination. tried below 3 methods and all have failed
IfFileExists "$Temp\test.dll" 0 new_installation
StrCpy $ALREADY_INSTALLED 1
new_installation:
SetOverwrite on
!insertmacro InstallLib DLL $ALREADY_INSTALLED REBOOT_PROTECTED "$Temp\test.dll" "$WINDIR\test.dll" "$WINDIR"
System::Call "kernel32::MoveFileEx(t '$TEMP\test.dll', t '$WINDIR\test.dll', i 5)"
Rename /REBOOTOK '$TEMP\test.dll' `$WINDIR\test.dll`
We confirmed elsewhere that your direct call to MoveFileEx succeeds. This should mean that the rename operation has been recorded in the registry. You can confirm this with the PendMoves tool from SysInternals. That same page also provides a MoveFile tool you can try.
Assuming the operation has been recorded, you should investigate the reasons the file operation might fail during boot:
One or both files are read-only.
The security descriptor (ACL) blocks the move and/or delete operation.
The file you are trying to replace is a protected system file.
File is locked by the system or a driver.
According to this answer, Process Monitor boot logging is active before the rename operation begins so you could use it to see if it provides any clues as to why the operation fails...

Calling WriteRegMultiStr in NSIS properly

With version 3.02 of NSIS came the addition of the WriteRegMultiStr function. When the function is called in my script the script throws an error:
Usage: WriteRegMultiStr /REGEDIT5 rootkey subkey entry_name hex_string_like_660000000000
root_key=(HKCR[32|64]|HKLM[32|64]|HKCU[32|64]|HKU|HKCC|HKDD|HKPD|SHCTX)
The call itself looks like this:
WriteRegMultiStr /REGEDIT5 HKLM "System\CurrentControlSet\Services\SomeService" "DependsOnService" "service1 service2"
Since there is no documentation on this specific function which was added later on, long after WriteRegStr and WriteRegDWORD were available, I have to wonder - how does one use it?
So far with respect to entering REG_MULTI_SZ values I only found the directive to use a registry-NSIS -plugin. Yet the function exists, so how can it be used?
Addendum:
Encoding the string to hex and passing it with ot without quotation marks yields no desirable result either.
I was actually able to find an answer after digging through the depths of the internet. Since I don't think this has been answered on StackOverflow I will leave a response here, in case anyone wants to use this function.
The structure of the command as described in the opening post is basically correct, but the value must be encoded precisely. My command looks like this:
WriteRegMultiStr /REGEDIT5 HKLM "System\CurrentControlSet\Services\SomeService" "DependsOnService" 54,00,63,00,70,00,69,00,70,00,00,00,41,00,66,00,64
For anyone intending to test this string, this is
Tcpip
Afd
encoded in hexadecimal regedit format. Precisely this is Regedit Version 5.0 format, as opposed to REGEDIT4 format. A conversion editor can be used to achieve this, I used OTConvertIt.
The script should then compile, assuming you run NSIS version 3.02 or higher.
As you found out, the value data must be in the exact same format as .reg files from Windows 2000+.
The reason this instruction works this way is because it is actually the same as WriteRegBin under the hood and very little code was added to support this new functionality.
In the future you might be able to drop the /REGEDIT5 switch and give it plain strings but support for that has not been added yet.
The Registry plug-in does allow you to write these strings in a sane manner.

In Visual C++ is there a way to stop Registry redirection from transforming system32 to syswow64?

I have written a program in Visual C++ that checks a particular reg_expand_sz value in the registry for changes. The default value is %SystemRoot%\System32\NOTEPAD.EXE. If the value is changed, the program will reset it back to the default data. The keys that I am modifying are either shared or reflected depending on the version of Windows, so it doesn't matter what Registry view (32-bit or 64-bit) I modify, as the keys will be synched up.
My problem that I am having is that if I am on a 64-bit version of Windows and use a 32-bit version of the program to change the value's data to %SystemRoot%\System32\NOTEPAD.EXE, due to the Registry redirection the data will instead be saved as %SystemRoot%\SysWOW64\Notepad.exe. This is not the correct default data though. If I compile the program in 64-bit, it works as expected. I would rather, though, if possible have a single 32-bit version that could reset the defaults regardless of whether the OS is 32-bit or 64-bit.
Does anyone know of a way to disable this transformation of System32 to SysWOW64 in reg_sz/reg_expand_sz values or know another method that I can use to achieve this task? I could write out a registry file and use system() to to execute reg or regedit and import it, but would rather do everything within the confines of the program itself.
Below is my current code:
HKEY hKey;
DWORD size;
LONG lResults;
wchar_t szVdata[] = L"%SystemRoot%\\System32\\NOTEPAD.EXE %1";
size = sizeof(szVdata);
lResults = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Classes\\batfile\\shell\\edit\\command",0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &hKey);
if(lResults == ERROR_SUCCESS)
RegSetValueExW (hKey, L"",0, REG_EXPAND_SZ, (PBYTE)szVdata, size);
RegCloseKey (hKey);
Any help would be appreciated.
Try %SystemRoot%\\SysNative\\

How to Create Custom View in Windows Event Viewer using API?

Is there an API to create a Custom View in Windows Event Viewer?
I would like to do that in the installation using NSIS,
But if I cant and there is any other way then I'll write a plugin for it.
The problem is not NSIS but the way the Event Log works, you need to install a message table DLL somewhere on the system and register it in the registry. This DLL has to stay on the system since it is used by the Event Log Viewer when you view the log and is not used when you log the message! This design is really problematic for a installer.
It is probably better to just log to a plain text file in $temp or $exedir.
If you really want to use the Event Log and you don't mind hacks you could:
Put the message table DLL in $pluginsdir and register/unregister when the installer starts/ends (If it crashes you leave junk in the registry!)
Use a Microsoft/Windows message table (They are not documented, could change from version to version (And service packs?) so you would have to test every Windows version and configuration you support!)
The NSIS code looks something like this:
section
System::Call 'advapi32::RegisterEventSource(i0,t "Software Installation")i.r0'
#TODO: Check if $0 == 0 and display error message
System::Call '*(i,&t1000 "Hello world")i.r1'
IntOp $2 $1 + 4
System::Call '*$1(ir2)'
System::Call 'advapi32::ReportEvent(ir0,i ${EVENTLOG_ERROR_TYPE},i0,i 101,i0,i1,i0,ir1,i0)i'
System::Free $1
System::Call 'advapi32::DeregisterEventSource(ir0)'
sectionend

Detect if an instance is running with kernel32::CreateMutexA

I'm working on an NSIS installer, and trying to check if a certain application is running before uninstalling. So, I use kernel32::CreateMutexA call. Here is the chunk:
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "cmd.exe") i .r1 ?e'
Pop $R0
StrCmp $R0 0 +3
MessageBox MB_USERICON "The application is already running."
Abort
I put it into un.onInit. Trouble is, the process (cmd.exe here) is never detected.
Did I miss something?
Tx.
I found a simple solution; using FindProcDLL plugin.
So:
FindProcDLL::FindProc "cmd.exe"
Pop $R0
StrCmp $R0 0 +3
MessageBox MB_USERICON "The application is already running." IDOK
Abort
P.S. FindProcDLL.dll must be copied into /Plugins.
All you are doing is creating a mutex with the global name "cmd.exe". From the MSDN article for CreateMutex:
If lpName matches the name of an existing event, semaphore, waitable
timer, job, or file-mapping object, the function fails and the
GetLastError function returns ERROR_INVALID_HANDLE. This occurs
because these objects share the same name space.
So unless cmd.exe creates a handle to one of those types of objects with the name "cmd.exe", this call will simply create a new mutex with that name and return you the (non-erronous) handle.
You're probably using the wrong Win32 API function. Your CreateMutex tries to create a named mutex "something.exe". If there isn't one with that name it will succeed, so unless the process you're trying to check is creating a mutex with that name, you won't get the result you're after.
What you want is probably to enumerate all running processes and see if the one you're after is there. You can do this with ToolHelp32 from Win32 API - see sample here. I don't know how easy it will be to convert it to 'pure' NSIS so you might want to write a DLL plugin, or check if there's an existing solution floating around the NSIS community.
For this kind of thing, I've used either the KillProcess or Find-Close-Terminate plugins for NSIS - see here
Documentation is pretty straightforward, hopefully this does what you need - with a fairly minimal overhead.

Resources