In my Wix project, I need to set an external application as the default program for a new file type, so I add some file associations in registry keys(weird I know, but I'm developing a plugin and I don't find a native way to deal with external programs).
MSDN says we should call SHChangeNotify if we change file associations. If not, the new association will not work until the system restarts. Here is my problem, how can I do this in Wix? I find a tool which implements this feature but what I need is hard code this in Wix Installer.
[solution] At first I add ProgId element the way #BdN3504 shows. Then I use Custom Action to send SHChangeNotify. Cheers~
Have you had a look at this answer?
You have to first locate the target application in a FileSearch and then reference that in an Extension element.
<Property Id="TARGETEXE">
<DirectorySearch Path="C:\Program Files (x86)\App"
Depth="0"
AssignToProperty="no"
Id="NppSearch">
<FileSearch Name="Target.exe"
Id="targetExeFileSearch" />
</DirectorySearch>
</Property>
<ProgId Id='Fileassoc.assoc' Description='File extension description'>
<Extension Id='assoc' ContentType='application/assoc'>
<Verb Id='open' Command='Open' TargetProperty='TARGETEXE' Argument='"%1"' />
</Extension>
</ProgId>
See the documentation for the
DirectorySearch Element
FileSearch Element
ProgId Element
Extension Element
and the Verb Element
Related
I'm trying to create a .msi installer for a Node.js project using the WiX Toolset. I want to remove the node_modules folder during uninstall. This is the relevant excerpt from my .wxs file:
<Property Id='APPLICATIONFOLDER'>
<RegistrySearch Id='ApplicationFolder' Root='HKLM' Key='Software\[ProductName]' Name='InstallDir' Type='raw'/>
</Property>
<DirectoryRef Id='TARGETDIR'>
<Component Id='RemoveFolder' Guid='3e2231d9-61b3-41a2-b407-8df015be537e'>
<util:RemoveFolderEx Property='[APPLICATIONFOLDER]\node_modules' On='uninstall'/>
<RegistryValue Root='HKLM' Key='Software\[ProductName]' Name='InstallDir' Type='string' Value='INSTALLDIR' KeyPath='yes'/>
</Component>
</DirectoryRef>
I thought this would read the value from the registry, assign it to the property APPLICATIONFOLDER, and then in the RemoveFolderEx tag append the string \node_modules to that property's value.
But when I try to run this through light.exe, I get ICE03: Invalid identifier; Table: WixRemoveFolderEx, Column: Property, Key(s): wrf1A1445CB13E8BF98989EA24E3514470A.
Clearly, it is unhappy with my Property attribute, but what do I need to fix it? I've seen plenty of guides using this general pattern, but they always read the install dir value from the registry and then nuke that entire folder. That seems a little crass - if the user has added files to the directory I don't want to delete them. How can I specify a subfolder to delete?
I've solved the problem. Because I personally had a hard time figuring it out - the guides I've seen on RemoveFolderEx only all cover the same use case of nuking a folder saved to the registry, not a subfolder; and I find the WiX documentation to be less than beginner-friendly in most cases -, I am recording the solution here in case someone else should ever stumble upon the same problem.
As was pointed out already, Property does not work in the way I presumed in my question - it really does just accept the id of a property holding the path to the directory to delete. So you will definitely need to set a property in some way. (Some guides on the internet do not make this very clear.)
The next obvious path to investigate would be to assign the property a modified result of the registry search. However, this is impossible with WiX (to the best of my knowledge).
The answer lies in SetProperty. This, finally, allows to set a new property based on the one read from the registry.
It is worth noting that doing this still prevents the installation folder from being deleted, even if it empty. This, however, can easily be remedied by adding a RemoveFolder (without the Ex). This will delete the folder, but only if it is empty.
The relevant code that got it working for me is below.
<!-- Read the InstallDir from the registry and assign it to property APPLICATIONFOLDER -->
<Property Id='APPLICATIONFOLDER'>
<RegistrySearch
Id='ApplicationFolder'
Root='HKLM'
Key='Software\[ProductName]'
Name='InstallDir'
Type='directory'
Win64='yes'/>
</Property>
<!-- Set new property NODE_MODULES_FOLDER by appending node_modules\ to APPLICATIONFOLDER -->
<SetProperty
Id='NODE_MODULES_FOLDER'
Value='[APPLICATIONFOLDER]node_modules\'
Before='CostInitialize'/>
<Component Id='RemoveFolders' Guid='...'>
<!-- Remove the node_modules subfolder, using the property we created above -->
<util:RemoveFolderEx Property='NODE_MODULES_FOLDER' On='uninstall'/>
<!-- Remove main installation directory now, but only if it is empty. -->
<!-- That's why we're using RemoveFolder instead of RemoveFolderEx here. -->
<RemoveFolder Id='RemoveMainFolder' Property='APPLICATIONFOLDER' On='uninstall'/>
<RegistryValue
Root='HKLM'
Key='Software\[ProductName]'
Name='InstallDir'
Type='string'
Value='[INSTALLDIR]'
KeyPath='yes'/>
</Component>
I thought this would read the value from the registry, assign it to the property APPLICATIONFOLDER, and then in the RemoveFolderEx tag append the string \node_modules to that property's value.
It won't. As the doc says, Property is:
The id of a property that resolves to the full path of the source directory.
The easiest way to make that happen is to write the exact path you want to nuke to the registry.
Is there any way to find (and execute) a file that lies near installer ? My example of file structure:
MyInstaller.msi
Tools
Tool.exe
I want to be able to find Tools\Tool.exe from MyInstaller.msi and launch it (if it exists) when user will click a button. I'm trying this
<Property Id="TOOL_EXE">
<DirectorySearch Id="TOOL_PATH" Path="\Tool\">
<FileSearch Id="TOOL_EXE_SEARCH" Name="tool.exe" />
</DirectorySearch>
</Property>
but TOOL_EXE variable is empty.
Important note: I don't have this file in moment of creating installer, so I can't include it.
I think the only problem here is the Path-attribute, where you specified \Tool\ as value. The leading backslash tries to search on the root, therefore it can't find the directory. Also leave away the final backslash: adding it the installer expects something to follow there. So try .\Tool instead.
You could also use the following (SourceDir is referencing the folder of the installation file, see also here). It is possible though that the property is not already available, didn't test this.
<Property Id="TOOL_EXE">
<DirectorySearch Id="TOOL_PATH" Path="[SourceDir]Tool" Depth="0">
<FileSearch Id="TOOL_EXE_SEARCH" Name="tool.exe" />
</DirectorySearch>
</Property>
I have created a wix build, which does the following
1. Install the files in temporary location.
2. Then I call a custom Action to copy the files into a different location and does messaging of some of the config files.
3. Display a message to user that installation is complete.
4. and then Exit the MSI.
PROBLEM: The reference to the MSI exists in control panel add remove program.
How can I remove the reference of the project from add remove program?
Is it possible within the same WIX build?
What are the alternatives to achieve it?
Thanks,
M
Assuming that you want to prevent your application from being displayed in the Add or Remove Programs list of Control Panel.
Then you need to set property ARPSYSTEMCOMPONENT in to 1.
<Property Id="ARPSYSTEMCOMPONENT" Value="1" />
Read more about ARPSYSTEMCOMPONENT
And also if you want it to show into add or remove programs, but remove or modify functions disabled then use ARP ENTRY properties.
<Property Id="ARPNOMODIFY" Value="1" />
<Property Id="ARPNOREPAIR" Value="1" />
<Property Id="ARPNOREMOVE" Value="1" />
Refer for More Info: ARP ENTRY
And if you don't want your program to be uninstall using msiexec also you can add condition like
<Condition Message="Uninstall is not supported">REINSTALL or Not Installed</Condition>.
Uninstall using msiexec.exe /x will give pop-up saying uninstall is not supported and will quit.
Background:
Our application is a plugin for a much larger application. Everything we create are DLL files that the larger application hooks to. Because of this our .NET (C#), DLL files must be registered for a COM interface.
We have a working InstallShield project, but for many reasons which I won't go into here, we wish to migrate it to WiX. The only thing left to be done for the installer is to register our DLL files for COM.
Problems
Here is a sample component with a single DLL file.
<Component Id="MyComponent" Guid="COMPONENT-GUID" SharedDllRefCount="yes" >
<File Id="MyDLL.dll" Name="MyDLL.dll" KeyPath="yes" Assembly=".net"
AssemblyManifest="MyDLL.dll" AssemblyApplication="MyDLL.dll" />
</Component>
As per the accepted answer of How do you register a Win32 COM DLL file in WiX 3?, it is recommended to add SelfRegCost=1 to the File tag. This results in an error during install:
Module C:\Program files\Product\MyDll.dll failed to register. HRESULT -2147024769. Contact your support personnel.
The second answer in the same question (by Rob Menshing) recommends against this approach, but to add inside the file tag this:
<Class Id="PUT-CLSID-HERE" Context="InprocServer32" ThreadingModel="apartment" Description="Your server description">
<ProgId Id="Your.Server.1" Description="Your ProgId description">
<ProgId Id="Your.Server" Description="Your ProgId description" />
</ProgId>
</Class>
<Class Id="PUT-PROXY-CLSID-HERE" Context="InprocServer32" ThreadingModel="both" Description="Your server Proxies, assuming you have them">
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface1" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface2" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface3" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface4" />
</Class>
I am a bit confused as to put in as the CLSID ids. I put in a generated GUID and installed it. It installed fine, but the larger application could not find the DLL files. (I used the interface tags generated from heat.exe.)
Another approach found in the same question (by Adan Tegen) recommends using
heat.exe file myDll.dll -out my.wxs
Using the output, I add this to the File tag:
<TypeLib Id="Another Guid" Language="0" MagorVersion="1">
<!--Interfaces generated from heat.exe-->
</TypeLib>
Every way I have tried to register .NET for COM has failed and after reading so many questions about the topic I am no closer to knowing the correct way of doing it. What should I do? Why is this such a difficult task when everything else in WiX has been fairly simple?
I should mention that the original InstallShield project created a custom action that would call regasm.exe. If all else fails that could be a possibility, but I would rather do things right.
I just found that heat.exe file myDll.dll -scom -o myDll.wxs outputs registry values similar to what I need. Now, how do I reference the newly created component inside the DLL component?
I just found that heat.exe file myDll.dll -scom -o myDll.wxs outputs registry values similar to what I need.
Are you sure about -scom there? AFAIK, that option actually suppresses COM registration!
Now how do I reference the newly created component inside the DLL component?
The component generated by heat.exe already includes a File element for the DLL. So it actually contains everything needed to install and register the DLL file. You don't need the original "DLL component".
If you need to put the file and its COM registration in separate components, then you will have to remove the File element from the component generated by heat.exe.
Also, components cannot reference other components. You can have references between ComponentGroups though, something that we use heavily in our wixlibs to model dependencies, but that's a rather advanced use case.
I found an example on registering DLLs, Registering an Assembly for COM Interop in a MSI file with the Windows Installer XML toolset., and WiX complains about the "AssemblyRegisterComInterop" attribute.
I removed that and changed the "Assembly" attribute to win32, and it says I need to specify the AssemblyManifest attribute, but what should I put there?
The easiest way (and Rob M will rant and rave about how this is wrong) is just to use SelfRegCost=1 on the File tag for the DLL.
This is wrong, because we should be explicitly controlling the registration of the DLL, not allowing it just to run arbitrary code via DllRegisterServer. The theory being that a DLL should do nothing beyond putting the appropriate entries in the registry when DllRegisterServer is called. Unfortunately, a lot of of them do more than that, so self-registration might be the only way to get your install to work.
It's also wrong, because that means the Windows installation system doesn't know anything about those registry keys, and what should and shouldn't be there. That means repairing won't work, and possibly un-installation won't clean up properly, etc.
Otherwise, you can generate the appropriate WiX code by pointing heat.exe at your DLL, and integrating its output into your current WiX project. You'll get a variety of Class, ProgID, TypeLib, and Registry tags. You may need to manually edit that output to get it to compile.
I hope that helps.
It isn't just me that will rant and rave about how SelfReg is evil. The MSI SDK gives you a list of seven reasons why not to use SelfReg.
Example:
<Component Id="Component" Guid="*">
<File Source="ComServer.dll">
<Class Id="PUT-CLSID-HERE" Context="InprocServer32" ThreadingModel="apartment" Description="Your server description">
<ProgId Id="Your.Server.1" Description="Your ProgId description">
<ProgId Id="Your.Server" Description="Your ProgId description" />
</ProgId>
</Class>
<Class Id="PUT-PROXY-CLSID-HERE" Context="InprocServer32" ThreadingModel="both" Description="Your server Proxies, assuming you have them">
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface1" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface2" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface3" />
<Interface Id="PUT-INTERFACEID-HERE" Name="IInterface4" />
</Class>
</File>
</Component>
Ultimately, Troy's answer is all correct.
Use the heat.exe program that comes with the WiX toolset, to generate the wxs registration fragment:
heat.exe file <filename> -out <output wxs file>
eg.
heat.exe file my.dll -out my.wxs
Copy the contents of the <Component> tag from that new wxs file and paste it into the <Component> of your existing wxs file that references your dll using a <File> tag. Delete the <File> tag you pasted and replace all references to that file Id with the one that was already there; if the existing one didn't have an Id attribute, add it and set it to whatever you want.