Custom Action After InstallInitialize to check the Drive Existence - installation

I have written a custom action in C# to check for the drive existence like below, I got stuck in between.
[CustomAction]
public static ActionResult MySimpleAction(Session session)
{
if (Directory.Exists("F:\\"))
{
return ActionResult.Success;
}
else
{
return ActionResult.Failure;
}
}
And in wxs file, I am running the custom action as like below.
<Binary Id="myAction" SourceFile="MyCustomAction.CA.dll" />
<CustomAction Id="myActionId"
BinaryKey="myAction"
DllEntry="MySimpleAction"
Execute="immediate"
Return="check" />
<InstallExecuteSequence>
<Custom Action="myActionId" After="InstallInitialize" > </Custom>
</InstallExecuteSequence>
If I run the msi in the target machine where I have F:\ drive then installation succeeds, if the target machine doesn't have F:\ drive then Setup failed, I am getting error as "Setup wizard ended prematurely because of an error. Your system has not been modified."
What I am trying to do here is, if F:\ drive is available in the target computer (My Custom action succeeds), I want to set my root drive as F:\, and I want to install the application in F:\MyApp\Bin
<Property Id="ROOTDRIVE"><![CDATA[F:\]]></Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="INSTALLFOLDERLOCATION" Name="MyApp">
<Directory Id="INSTALLLOCATION" Name="Bin">
if F:\ drive is not available in the target computer (My Custom action fails), I want to set my root drive as C:\, and I want to install in C:\MyApp\Bin
<Property Id="ROOTDRIVE"><![CDATA[C:\]]></Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="INSTALLFOLDERLOCATION" Name="MyApp">
<Directory Id="INSTALLLOCATION" Name="Bin">
How can I set the root drive property by using this custom action?
Thanks for the help!

When using an immediate Custom Action, you can set property values by using session["PROPERTYNAME"] so in your case you could use session["ROOTDRIVE"] = "F:\\"; in your Custom Action.
The reason it's failing at the moment is that you are returning a Failure message from your custom action, and since you have specified Return="check", the installer checks the return value, and fails the install if the Custom Action has failed.

You are on the right track. Here's what I do differently.
1) I use the DriveInfo class to see if the drive exists and it's of DriveType Fixed. (Not CDROM, USB Drive, Network....)
2) The custom action is scheduled in both the UI and Execute sequence after AppSearch and sets a property called something like InstallDirOverride. The custom action always returns ActionResult.Success.
3) I use a Set Property custom action (wxs element) to assign InstallDirOverride to INSTALLLOCATION (or INSTALLDIR... whatever you have called your main directory ) with the condition that INSTALLLOCATION doesn't yet have a value and InstallDirOverride does have a value and Not Installed. This custom action gets scheduled in both the UI Sequence and ExecuteSequence prior to CostInitialize.
The result of all this is an installer that defaults to C:\Program Files\My Company\My Product but changes it's behavior to default to something else based on the business rules in your C# custom action. This gives you the flexibility to default the way you want for a specific platform environment and yet still be complaint to Windows Standards when your platform is missing that resource.

I thank Christopher Painter and ChrisPatrick for helping me!!! the below code made the trick to work.
[CustomAction]
public static ActionResult MySimpleAction(Session session)
{
session.Log("DriveInfo Starts");
DriveInfo[] drives = DriveInfo.GetDrives();
foreach (DriveInfo d in drives)
{
if (d.Name.Contains("F") & d.IsReady == true & d.DriveType.ToString() == "Fixed")
{
session["TARGETDIR"] = "F:\\";
}
else
{
session["TARGETDIR"] = "C:\\";
session.Log("No F:\\ Drive Found!!!!");
}
}
session.Log("DriveInfo Ends");
return ActionResult.Success;
And in the .wxs file,
<Binary Id="myAction" SourceFile="MyCustomAction.CA.dll" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="INSTALLFOLDERLOCATION" Name="MyApp">
<Directory Id="INSTALLLOCATION" Name="Bin">
<CustomAction Id="myActionId" BinaryKey="myAction" DllEntry="MySimpleAction" Execute="immediate" Return="check" />
<InstallUISequence>
<Custom Action="myActionId" Before="CostFinalize" > NOT Installed </Custom>
</InstallUISequence>

Related

Wix add entered path to registry

I met a problem while adding entered by user path to registry.
Here are important parts of wix code.
Property declaration:
<Property Id="PathProp">C:\</Property>
Directory declaration:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="PathProp" Name="Name">
</Directory>
</Directory>
Registry block:
<RegistryKey Root="HKLM" Key="SOFTWARE\Path\To\Key" Action="createAndRemoveOnUninstall">
<RegistryValue Name="UserSetPath" Value="[PathProp]" Type="string" />
</RegistryKey>
I have the dialog which pulls PathProp from user and I want entered value be in registry. But I see the default value ("C:\"). Is it possible to register exactly what user entered?
I had a experience in past, where installer properties set in UI mode were destroyed in Execute mode.
Reason:
Properties were not set "secure".
Wix properties have attribute secure="true". Use It.
Also if possible share the installer log with us,so we can have a look at it.
You can get log by using this command:
<InstallerPath>/<InstallerName>.msi /l*v <LogPath>/InstallLog.txt

How to set random name for install directory for msi (wix)

Wix developers!
Is it possible to specify random name for install directory name with the help of property or vbscript custom action?...
I have the following Product.wxs:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="Client">
<Component Id="ProductComponent">
<File Id="File001" Source="..\Release\One.dll" />
</Component>
...
It is prepare install directory like:
C:\Program Files(x86)\Client
I need like this
C:\Program Files(x86)\234wfdasdqaw
where "234wfdasdqaw" random string which generated in every case when
a) run msi for installation
b) and if admin is not set the INSTALLLOCATION via command line.
Is it possible?
thank you.
You'll need a custom action to randomly generate the string and then a type 51 (set property) custom action to set INSTALLLOCATION to a computed value if 1) it doesn't already have a value and 2) the product isn't already installed. I would not do this in the root of ProgramFilesFolder as that would be very messy. At least do it in [ProgramFilesFolder]Company or [ProgramFilesFolder]Product. Your question makes me wonder if you plan on installing this MSI more then once. Be aware that it takes special tricks to install an MSI multiple times.

Wix Installer - Create Folder hierarchy based on Property

I am using Wix 3.6 to create a setup. I am still learning as I go along. The information out there is still scattered around. I am just waiting for my Wix Developer Guide book arriving.
I currently have a custom UI dialog where the user enters some application configuration. Part of that configuration is to specify a log folder. This at present this just sets a property [LogFolder]. This is defaulted to something like D:\Logs.
I want the installer to create that directory when the setup is run. I have the following to try and do this but it just created a folder named [LOGFOLDER] on the D: drive when I run the setup.
<Product ...
<Directory Id="TARGETDIR" Name="SourceDir" >
<Directory Id="LogFolderDir" Name="[LOGFOLDER]" >
<Component Id="LogFolderComponent" Guid="{7E7D6916-B321-40D6-ABAD-696B57A6E5FB}" KeyPath="yes">
<CreateFolder />
</Component>
</Directory>
</Directory>
...
</Product>
How can I do this with Wix?
The first step is create a property set to the value you want:
<Product>
<Property Id="LOGFOLDER" Value="D:\Logs" />
</Product>
The second step is to create a dialog where you set this property (or another thing to change its value):
<Dialog>
<Control Id="Edit_LogFolder" Type="Edit" Property="LOGFOLDER" />
</Dialog>
Then you need to change your directories structure to create this folder in a default location:
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="MyApp">
<Directory Id="LOGFOLDER" Name="Logs" />
</Directory>
</Directory>
The last step is to create a Component that will create the directory, like this:
<ComponentGroup Id="ComponentGroup_LogFolder">
<Component Id="Component_LogFolder" Guid="" Directory="LOGFOLDER">
<CreateFolder Directory="LOGFOLDER" />
</Component>
</ComponentGroup>
Remark:
If D:\ is a disc drive and you have a disc inserted, the installation will fail because it will try to create the folder and it won't succeed.
The Name attribute isn't formattable so you can use properties in it. The Id 'LogFolderDir' doesn't have a parent such as "ProgramFilesFolder' so it's defaulting to the volume with the largest amount of disk space. In this case D but YMMV.
It's dangerous to default to D: because D: might not exist. How I'd set this directory up is Id="LOGDIR" Name="Logs" and make it a child of the INSTALLDIR/INSTALLLOCATION directory element. Then in your custom UI, wire up another BrowseFolder dialog to give the user the ability to override it. Or, make it associated with a required Logs feature so that the stock feature selection dialog can be used to select the feature and browse the destination folder.
If you still want it to "default" to D:\Logs what I would do is have a custom action that checks to see if D: exists and is a fixed disk. If so, set the LOGDIR=D:\Logs
There is a simpler solution by using the same ID for the property and the directory (without naming the directory). But you must use the full path of the folder in the property.
Let's say the log directory is C: \ ProgramDirectory \ Data \ Log and you want to set Data with a property (usually if the value of Data is different and conditionally set).
<Property Id="PR_DATA_DIRECTORY" Value="C:\ProgramDirectory\Data" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DIR_PROGRAM" Name="C:\ProgramDirectory" >
<Directory Id="PR_DATA_DIRECTORY">
<Directory Id="DIR_LOG" Name="Log" />
<Directory/>
</Directory>
</Directory>

Running copied files in WiX using a custom action

I'm creating an MSI installer using WiX and I have, say, a *.bat file that I'm copying to SomeFolder2 under %temp% (something like the code snippet below...)
...
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='MyDir' Name='SomeFolder'>
<!-- %TEMP -->
<Directory Id="TempFolder" Name="TmpFolder">
<Directory Id='MyDir2' Name='SomeFolder2'>
<!-- CREATE THE %TEMP%\SomeFolder2 FOLDER -->
<Component Id='FolderComponent' Guid='{GUID}'>
<CreateFolder />
</Component>
<Component Id='CheckComponent' Guid='{GUID}'>
<File Id='mybat' Name='mybat.bat' DiskId='1' Source='.\mybat.bat' KeyPath="yes">
<Shortcut Id="mybatShcut"
Directory="ProgramMenuDir"
Name="{name}"
WorkingDirectory='INSTALLDIR'
Advertise="yes" />
</File>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
...
Now, to run this, I have two custom actions (DESTDIR is %TEMP%\SomeFolder2):
<CustomAction Id="SetPath" Property="ThePath" Value="[DESTDIR]\mybat.bat" />
<CustomAction Id="StartAction" Property="ThePath" ExeCommand="" Return="asyncNoWait" />
Then in the install sequence:
<InstallExecuteSequence>
<Custom Action="SetPath" After="{some standard MS action}">NOT REMOVE="ALL"</Custom>
<Custom Action="StartAction" Before="{some other action}">NOT REMOVE="ALL"</Custom>
...
</InstallExecuteSequence>
I've put SetPath to run after a variety of standard actions (for example, PublishProduct) while StartAction will come before another custom action.
When I run the MSI file, I look in the log and ThePath does get set with the correct path. However, when StartAction is run, I get this error:
Return value 1631.
which, according to the documentation, translate to "ERROR_CREATE_FAILED" (the Windows Installer service failed to start. Contact your support personnel). The thing is, the file did get copied to %TEMP%\SomeFolder2 (before the setting of the path and the actual exection, might I add...), but for some reason, it doesn't execute at all (if you do execute it manually or via the command prompt or whatnot, it does execute normally).
I tried putting the same file under ProgramFiles\Some_Directory_For_The_Program. The same thing happens; it gets copied there, but it doesn't execute. Why is this happening?
First off, as long as you'd like to use a file installed by your package in a custom action, you should make it deferred. That is, StartAction CA in your example must be deferred. Also, I try to use QtExec standard CA when I need to run executables from CA.
I hope this helps.

How to run a script in WiX with a custom action - simplest possible example?

Newbie WiX question: How do I
1. Copy a single-use shell script to temp along with the installer
e.g.
<Binary Id='permissions.cmd' src='permissions.cmd'/>
2. Find and run that script at the end of the install.
e.g.
<CustomAction Id='SetFolderPermissions' BinaryKey='permissions.cmd'
ExeCommand='permissions.cmd' Return='ignore'/>
<InstallExecuteSequence>
<Custom Action="SetFolderPermissions" Sequence='1'/>
</InstallExecuteSequence>
I think I have at least three problems:
I can't find permissions.cmd to run it - do I need [TEMPDIR]permissions.cmd or something?
My Sequence comes too soon, before the program is installed.
I need cmd /c permissions.cmd somewhere in here, probably near ExeCommand?
In this example permissions.cmd uses cacls.exe to add the interactive user with write permissions to the %ProgramFiles%\Vendor ACL. I could also use secureObject - that question is "How do I add the interactive user to a directory in a localized Windows"?
Here's a working example (for setting permissions, not for running a script):
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="BaseDir" Name="MyCo">
<Directory Id="INSTALLDIR" Name="MyApp" LongName="MyProd">
<!-- Create the folder, so that ACLs can be set to NetworkService -->
<Component Id="TheDestFolder" Guid="{333374B0-FFFF-4F9F-8CB1-D9737F658D51}"
DiskId="1" KeyPath="yes">
<CreateFolder Directory="INSTALLDIR">
<Permission User="NetworkService"
Extended="yes"
Delete="yes"
GenericAll="yes">
</Permission>
</CreateFolder>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
Note that this is using 'Extended="Yes"' in the Permission tag, so it's using the SecureObjects table and custom action not the LockPermissions table (see WiX docs for Permission Element). In this example the permissions applied to the MyProd directory by SecureObjects are inherited by subdirectories, which is not the case when LockPermissions is used.
I found the blog post From MSI to WiX, Part 5 - Custom actions: Introduction helpful when I wanted to understand CustomActions in WiX.
You can also find the definition of CustomAction and its attributes in CustomAction Element.
You need to do something like this
<CustomAction Id="CallCmd" Value="[SystemFolder]cmd.exe" />
<CustomAction Id="RunCmd" ExeCommand="/c permission.cmd" />
<InstallExecuteSequence>
<Custom Action="CallCmd" After="InstallInitialize" />
<Custom Action="RunCmd" After="CallCmd" />
</InstallExecuteSequence>
Have you got an example of how this is used? I mean, do use CreateFolder nested under the directory whose ACL I want to change? Or do I use CreateFolder first, separately? Is the following even close?
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id="directory0" Name="MyApp" LongName="My Application">
<Component Id="component0" DiskId="1" Guid="AABBCCDD-EEFF-1122-3344-556677889900">
<CreateFolder>
<Permission User='INTERACTIVE'
GenericRead='yes'
GenericWrite='yes'
GenericExecute='yes'
Delete='yes'
DeleteChild='yes' />
<Permission User='Administrators' GenericAll='yes' />
</CreateFolder>
<File Id="file0" Name="myapp.exe" Vital="yes" Source="myapp.exe">
<Shortcut Id="StartMenuIcon" Directory="ProgramMenuFolder" Name="MyApp" LongName="My Application" />
</File>
</Component>
<Directory Id="directory1" Name="SubDir" LongName="Sub Directory 1">
<Component Id="component1" DiskId="1" Guid="A9B4D6FD-B67A-40b1-B518-A39F1D145FF8">
etc...
etc...
etc...
</Component>
</Directory>
</Directory>
</DirectoryRef>
</Fragment>
Rather than running custom action you can try using Permission element as a child of CreateFolder element, e.g.:
<CreateFolder>
<Permission User='INTERACTIVE' GenericRead='yes' GenericWrite='yes'
GenericExecute='yes' Delete='yes' DeleteChild='yes' />
<Permission User='Administrators' GenericAll='yes' />
</CreateFolder>
Does this overwrite or just edit the ACL of the folder?
According to MSDN documentation it overwrites:
Every file, registry key, or directory that is listed in the LockPermissions Table receives an explicit security descriptor, whether it replaces an existing object or not.
I just confirmed that by running test installation on Windows 2000.
Most people tend to steer clear of the lockPermissions table as it is not additive, meaning it will overwrite your current permissions (from a managed environment perspective, this is bad). I would suggest you use a tool which supports ACL inheritance such as SUBINACL or SETACL or one of the many ACL tools.
In relation to why your earlier posts failed there is a few reasons. There are four locations where you can put your custom actions (CAs): UI, Immediate, Deferred, and Commit/Rollback.
You need your CA to set permissions in the deferred sequence, because the files are not present until midway through the deferred sequence. As such, anything prior will fail.
Your setup is in immediate (so will fail)
Your setup is at sequence of 1 (which is not possible to be deferred so will fail)
You need to add an attribute of Execute="Deferred" and change sequence from "1" to:
<Custom Action="CallCmd" Execute="Deferred" Before="InstallFinalize" />
This will ensure it's done after the files are installed, but prior to the end of the deferred phase (the desired location).
I would also suggest you call the EXE file directly and not from a batch file. The installer service will launch and the EXE file directly in the context you need. Using a batch file will launch the batch file in the correct context and potentially lose context to an undesired account while executing.

Resources