Run PowerShell script from WiX installer - powershell

I have found a couple of examples showing how to run a PowerShell script from WiX but have not been successful running either of them. So, I'd like to post what I have with the hope that someone can point out what I am doing wrong.
<!--Install the PowerShell script-->
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="cmp_ShutdownIExplore" Guid="{4AFAACBC-97BB-416f-9946-68E2A795EA20}" KeyPath="yes">
<File Id="ShutdownIExplore" Name="ShutdownIExplore.ps1" Source="$(var.ProjectDir)Source\PowerShell\ShutdownIExplore.ps1" Vital="yes" />
</Component>
</DirectoryRef>
<!--Define the CustomAction for running the PowerShell script-->
<CustomAction Id="RunPowerShellScript" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="check" Impersonate="yes" />
<InstallExecuteSequence>
<!--Invoke PowerShell script -->
<Custom Action="RunPowerShellScript" After="InstallFiles"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
<!-- Define custom action to run a PowerShell script-->
<Fragment>
<!-- Ensure PowerShell is installed and obtain the PowerShell executable location -->
<Property Id="POWERSHELLEXE">
<RegistrySearch Id="POWERSHELLEXE"
Type="raw"
Root="HKLM"
Key="SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell"
Name="Path" />
</Property>
<Condition Message="This application requires Windows PowerShell.">
<![CDATA[Installed OR POWERSHELLEXE]]>
</Condition>
<!-- Define the PowerShell command invocation -->
<SetProperty Id="RunPowerShellScript"
Before ="InstallFiles"
Sequence="execute"
Value =""[POWERSHELLEXE]" -Version 2.0 -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -Command "& '[#ShutdownIExplore.ps1]' ; exit $$($Error.Count)"" />
</Fragment>
When I run the installer I have created I get the following error (from log):
MSI (s) (DC:F8) [11:21:46:424]: Executing op: ActionStart(Name=RunPowerShellScript,,)
Action 11:21:46: RunPowerShellScript.
MSI (s) (DC:F8) [11:21:46:425]: Executing op: CustomActionSchedule(Action=RunPowerShellScript,ActionType=1025,Source=BinaryData,Target=CAQuietExec,)
MSI (s) (DC:9C) [11:21:46:459]: Invoking remote custom action. DLL: C:\Windows\Installer\MSI8228.tmp, Entrypoint: CAQuietExec
CAQuietExec: Error 0x80070057: failed to get command line data
CAQuietExec: Error 0x80070057: failed to get Command Line
CustomAction RunPowerShellScript returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Action ended 11:21:46: InstallFinalize. Return value 3.
I am not at all clear what this error is trying to say. Are my internal references bad? Is the command to execute the script bad? Something else?
Any help is most appreciated and thanks in advance.

Looks like you have scheduled the CAQuietExec action as deferred. In this case you have to pass the command line to be executed via a CustomActionData property called QtExecDeferred which is written to the execution script. The deferred action can then access the property from the script.
More details at http://wixtoolset.org/documentation/manual/v3/customactions/qtexec.html

I didn't understand Stephen's answer, however I eventually got it working with the help of this blog post.
Here's a summary of the change I made to Greg's code to get it to work:
I changed CAQuietExec to WixQuietExec (I'm not sure if this was necessary).
In SetProperty I changed the value of the Before attribute from InstallFiles to the Id of the custom action; in Greg's case it would be RunPowerShellScript.
Although unrelated to the question, I ended up needing to change the -Version of powershell to 3.0 from 2.0 to prevent an error when running my script.
Here was my actual working code:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product Id="*" Name="..." Language="1033" Version="..." Manufacturer="..." UpgradeCode="...">
<Property Id="POWERSHELLEXE">
<RegistrySearch Id="POWERSHELLEXE"
Type="raw"
Root="HKLM"
Key="SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell"
Name="Path" />
</Property>
<Condition Message="This application requires Windows PowerShell.">
<![CDATA[Installed OR POWERSHELLEXE]]>
</Condition>
<SetProperty Id="InstallMongoDB"
Before ="InstallMongoDB"
Sequence="execute"
Value=""[POWERSHELLEXE]" -Version 3.0 -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -Command "& '[#MONGODB_INSTALL.PS1]' ; exit $$($Error.Count)"" />
<CustomAction Id="InstallMongoDB" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Return="check" Impersonate="yes" />
<InstallExecuteSequence>
<Custom Action="InstallMongoDB" Before="InstallFinalize"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
<Component Id="MONGODB_INSTALL.PS1" Guid="..." DiskId="1">
<File Id="MONGODB_INSTALL.PS1" Name="mongodb-install.ps1" Source="mongodb-install.ps1"/>
</Component>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="APPLICATIONFOLDER" Name="...">
<Directory Id="InstallScripts" Name="InstallScripts">
<Component Id="MONGODB_INSTALL.PS1" Guid="..." DiskId="1">
<File Id="MONGODB_INSTALL.PS1" Name="mongodb-install.ps1" Source="mongodb-install.ps1"/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Fragment>
</Wix>

Only the following example helped me
https://github.com/damienbod/WiXPowerShellExample/blob/master/SetupWithPowerShellScripts/Product.wxs
you need to add smth similar into your 'Product.wxs'. the 'Value' property of the first 'CustomAction' contains a ps script (create and run a windows service in my case).
<!-- assign the string (ps command) to RegisterPowerShellProperty -->
<CustomAction Id="RegisterWindowsService"
Property="RegisterPowerShellProperty"
Value=""C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NonInteractive -InputFormat None -NoProfile sc.exe create MyService binpath= 'C:\Program Files (x86)\My service\MyService.exe';sc.exe start MyService"
Execute="immediate" />
<!-- Deferred execution of the above script -->
<CustomAction Id="RegisterPowerShellProperty"
BinaryKey="WixCA"
DllEntry="CAQuietExec64"
Execute="deferred"
Return="check"
Impersonate="no" />
<InstallExecuteSequence>
<!-- On installation we register and start a windows service -->
<Custom Action="RegisterWindowsService" After="CostFinalize">NOT Installed</Custom>
<Custom Action="RegisterPowerShellProperty" After="InstallFiles">NOT Installed</Custom>
</InstallExecuteSequence>
you will need to add a reference to 'WixUtilExtension' in order to run the script.

Related

Controlling the sequence of events in a Wixtoolset (.msi) installer

I am creating a Microsoft Installer (.msi file) using the Wixtoolset (Windows Installer XML). This installer must automate the installation of an existing .exe program (named installer.exe below) and copy a custom configuration file (named settings.conf below) to the target directory. In addition the installer must modify the configuration file using the InstallFiles command below. But the timing of events is critical. If the executable installer runs too early, it fails or exhibits strange behavior. And if the executable installer run too late in the install sequence, it overwrites my modified configuration file with the generic values. I believe this can be done by assigning a string to the Before or After property value. What Before or After property assignment will allow the executable to run properly but not overwrite the configuration file I moved by the CopyFile element? Here is my Wixtoolset XML code.
<Property Id="CONFIGFOLDER" Value="C:\acme\config" >
<Feature
Id="ConfigurationFile"
Title="Configuration File"
Level="1"
<ComponentRef Id="CMP_ACME_Config_File" />
</Feature>
<DirectoryRef Id="TARGETDIR">
<Component Id="CMP_ACME_Config_File" Guid="">
<File
Id="ACME_Config"
Source="MySettings.conf"
KeyPath="yes"
<CopyFile Id="Copy_ACME_Config"
DestinationProperty="CONFIGFOLDER"
DestinationName="settings.conf" />
</File>
</Component>
</DirectoryRef>
<Binary
Id="InstallerEXE"
SourceFile="installer.exe" />
<CustomAction
Id="Launch_Installer"
BinaryKey="InstallerEXE"
Impersonate="yes"
Execute="deferred"
ExeCommand=""
Return="check" />
<InstallExecuteSequence>
<Custom Action="Launch_Installer"
Before="InstallFiles">
</Custom>
</InstallExecuteSequence>
</Property>
I can't explain exactly why this works but assigning "InstallFiles" to the "After" property in the "Custom" element seems to do the trick.
<InstallExecuteSequence>
<Custom Action="Launch_Installer"
After="InstallFiles">
</Custom>
</InstallExecuteSequence>

Wix Toolset execution of Powershell-script does not work

I want to run a PowerShell-Script before and after the installation with the msi-file. Below you can see the basic content of my configuration in Visual Studio 2013 for wix-project. The msi is compiled without errors and I can run the msi-file and go through the steps till the end of installation without errors.
In the log I can see, that the CustomAction had been started, but this cannot be true because the directory that should have been created by the underlying script has not been created.
If I run the script-file manually by powershell everything runs well. So the script itself should work and does not throw an error.
Any suggestions what is wrong here?
Wix-Project:
<Product Id="*" Name="MyAPP" Language="1031" Version="1.0.0.0" Manufacturer="Me" UpgradeCode="2A0A9FDB-9DD2-4058-8742-885EF63BFF37">
<!-- 6e8e53ce-66e4-4d97-900c-9678b83e44cc"> -->
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Languages="1031" Manufacturer="Me" Description="Installiert den MyApp auf ihr System" Comments="NOTHING TO COMMENT"/>
<MediaTemplate EmbedCab="yes" />
<!-- Major Upgrade Rule to disallow downgrades -->
<MajorUpgrade DowngradeErrorMessage="Eine neuere Version vom [ProductName] ist bereits installiert." />
<!-- ################################### -->
<!-- Aktionen vor installation ##########-->
<!-- ################################### -->
<InstallExecuteSequence>
<Custom Action="StartBatchFile" After="InstallInitialize"/>
<Custom Action="EndBatchFile" After="InstallFinalize"/>
</InstallExecuteSequence>
<CustomAction Id="StartBatchFile"
Property="RegisterHttpModulePowerShellProperty"
Value=""C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" -Version 2.0 -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -File "./BeforeInstallationScript.ps1" "[DIR_ComponentRef]""
Execute="immediate" />
<CustomAction Id="EndBatchFile"
Property="RegisterHttpModulePowerShellProperty"
Value=""C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" o-Versin 2.0 -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -File "./AfterInstallationScript.ps1" "[DIR_ComponentRef]""
Execute="immediate" />
<WixVariable Id="WixUIBannerBmp" Value="WixUIBannerBmp.bmp" />
<!-- Background bitmap used on the welcome and completion dialogs 493 × 312 -->
<WixVariable Id="WixUIDialogBmp" Value="WixUIDialogBmp.bmp" />
<!-- ################################### -->
<!-- User-Interface ####################-->
<!-- ################################### -->
<Property Id="WIXUI_INSTALLDIR">DIR_ComponentRef</Property>
<UIRef Id="WixUI_InstallDir" />
<UIRef Id="WixUI_ErrorProgressText" />
<!-- ################################### -->
<!-- Notwendige Abhaengigkeiten ########-->
<!-- ################################### -->
<PropertyRef Id="NETFRAMEWORK40FULL"/>
<Condition Message="Diese Anwendung benoetigt .NET Framework 4.0. Bitte installieren sie zuerst das .NET Framework und starten Sie die Installation erneut.">
<![CDATA[Installed OR NETFRAMEWORK40FULL]]>
</Condition>
<!-- ################################### -->
<!-- FEATURE-Installation ##############-->
<!-- ################################### -->
<Feature Id="FEATURE_MyApp" Title="MyApp" Description="Installiert die Datein des MyApps auf das System" Level="1" AllowAdvertise="no" ConfigurableDirectory="DIR_ComponentRef">
<ComponentRef Id="[...]"/>
[...]
</Feature>
</Product>
<Fragment>
<!-- ################################### -->
<!-- Ordner-Struktur ################### -->
<!-- ################################### -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="DIR_ComponentRef" Name="MyApp">
<Directory Id="DIR_CONFIGURATION" Name="configuration">
[...]
</Directory>
</Directory>
</Directory>
</Directory>
</Fragment>
<Fragment>
[...]
<!-- ################################### -->
<!-- Componenten-Definition ######## -->
<!-- ################################### -->
<DirectoryRef Id="DIR_ComponentRef">
<Component Id="CMP_MyApp.exe">
<File Id="MyApp.exe" Source="$(var.SourcePath)MyApp.exe" KeyPath="yes" Checksum="yes" />
</Component>
[...]
</DirectoryRef>
</Fragment>
</Wix>
Log-Result:
=== Protokollierung gestartet: 16.11.2016 10:41:12 ===
Aktion 10:41:12: INSTALL.
[...]
Aktion 10:41:35: StartBatchFile.
Aktion gestartet um 10:41:35: StartBatchFile.
Aktion beendet um 10:41:35: StartBatchFile. Rückgabewert 1.
[...]
Aktion beendet um 10:41:38: InstallFinalize. Rückgabewert 1.
Aktion 10:41:38: EndBatchFile.
Aktion gestartet um 10:41:38: EndBatchFile.
Aktion beendet um 10:41:38: EndBatchFile. Rückgabewert 1.
Aktion beendet um 10:41:38: INSTALL. Rückgabewert 1.
[...]
Let me know if you need further informations.
I'm not sure if the CustomActioncan find the PS1 file under the local directory. Try to adapt the WIX file with following blocks
<!-- Ensure PowerShell is installed and obtain the PowerShell executable location -->
<Property Id="POWERSHELLEXE" Secure="yes">
<RegistrySearch Id="POWERSHELLEXE"
Type="raw"
Root="HKLM"
Key="SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell"
Name="Path" />
</Property>
<Condition Message="This application requires Windows PowerShell.">
<![CDATA[Installed OR POWERSHELLEXE]]>
</Condition>
....
<CustomAction Id="RegisterPowershellCommandStop"
Property="CallPowerShellCommandStop"
Value=""[POWERSHELLEXE]" -NoLogo -NonInteractive -InputFormat None -ExecutionPolicy Bypass -NoProfile -File "[INSTALLFOLDER]ServiceStartStop.ps1" "[SERVICENAME]" "stop""
Execute="immediate" />
....
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="CMP_StartStopServicesScript" Guid="*">
<File Id="FILE_StartStopServicesScript" Source="!(wix.binDirectory)\ServiceStartStop.ps1" KeyPath ="yes" />
</Component>
...
In this case we're calling PS ("stored" in [POWERSHELLEXE]to start ServiceStartStop.ps1 (located under [INSTALLDIR]).
Hope that helps.

Wix execute Powershell WixQuietExec64

I'm creating an installer with Wix 3.10. This installer will need to execute a PowerShell script after the installation of the files.
To execute the PowerShell script I use the following:
<Component Id="AddUserInstallScript" Guid="{87DB934A-5ECF-4073-81F1-BA139F30A686}" Directory="PHONEMANAGER_FOLDER" >
<File Id="CreateADUserScript" Name="CreateADUser.ps1" Source="CreateADUser.ps1" KeyPath="yes"/>
</Component>
<Property Id="POWERSHELLEXE" Value="c:\Windows\System32\WindowsPowerShell\v1.0\powershell">
</Property>
<Condition Message="This application requires Windows PowerShell.">
<![CDATA[Installed OR POWERSHELLEXE]]>
</Condition>
<SetProperty Id="RunPSscriptCommand"
Before="RunPSscriptCommand"
Sequence="execute"
Value=""[POWERSHELLEXE]" -Version 3.0 -NonInteractive -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "&'[#CreateADUserScript]' -domainname '[SERVICE_USER_NETBIOSDOMAIN]' -password '[service_user_pwd]' -domainadminname '[DOMAIN_ADMINISTRATOR]' -domainadminpassword '[domain_administrator_pwd]' ; exit $$($Error.Count)" "
/>
<CustomAction Id="RunPSscriptCommand"
BinaryKey="WixCA"
DllEntry="WixQuietExec64"
Execute="deferred"
Return="check"
Impersonate="no"/>
<InstallExecuteSequence>
<Custom Action="RunPSscriptCommand" After="InstallFiles"><![CDATA[NOT Installed]]></Custom>
</InstallExecuteSequence>
When I run the installer I'm getting the following error in the log file:
MSI (s) (10:F8) [09:54:54:515]: Invoking remote custom action.
DLL: C:\Windows\Installer\MSI80E7.tmp, Entrypoint: WixQuietExec64
WixQuietExec64: Error 0x80070001: Command line returned an error.
WixQuietExec64: Error 0x80070001: QuietExec64 Failed
WixQuietExec64: Error 0x80070001: Failed in ExecCommon method
CustomAction RunPSscriptCommand returned actual error code 1603
(note this may not be 100% accurate if translation happened inside sandbox)
On the destination machines is PowerShell 3 installed. The script also uses PowerShell 3 modules.
I have included the option -InputFormat None but this makes no difference for PowerShell 3.
Any thoughts on this issue?
Try to run the script stand alone, in order to exclude command errors.
I have encountered same problem and what solved it was the command length (in your situation is RunPSscriptCommand's value), I have decreased the length to be less than 255 characters.
You can check your powershell version automatically using WixPSExtension.dll
<PropertyRef Id="POWERSHELLVERSION" />
<Condition Message="You must have PowerShell 1.0 or higher.">
<![CDATA[Installed OR POWERSHELLVERSION >= "1.0"]]>
</Condition>
I hope it will help.

WiX custom action using CAQuietExec fails with invalid command line error

I have a custom action that requires elevated privileges. The purpose of this custom action is to run sc.exe and remove the service triggers for a service that ships with Windows (w32time).
Here are the snippets of significance:
<Property
Id="removeW32TimeTrigger"
Value=""[SystemFolder]sc.exe" triggerinfo w32time delete"
/>
<CustomAction
Id="removeW32TimeTrigger"
BinaryKey="WixCA"
DllEntry="CAQuietExec"
Execute="deferred"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="removeW32TimeTrigger" After="InstallInitialize" />
</InstallExecuteSequence>
I followed the example for deferred execution here:
http://wixtoolset.org/documentation/manual/v3/customactions/qtexec.html
The error from the log appears to be having trouble with my syntax for where to find sc.exe.
Action 11:36:48: removeW32TimeTrigger.
CAQuietExec: Command string must begin with quoted application name.
CAQuietExec: Error 0x80070057: invalid command line property value
CAQuietExec: Error 0x80070057: failed to get Command Line
I'm clearly doing something wrong. Any help would be appreciated.
Since you are running the CA in deferred you need to send CustomActionData with a type 51 custom action instead of using Property.
Try this and see if it works:
<CustomAction Id='removeW32TimeTrigger_set'
Property='removeW32TimeTrigger'
Value='"[SystemFolder]sc.exe" triggerinfo w32time delete'
Execute='immediate'/>
<CustomAction
Id="removeW32TimeTrigger"
BinaryKey="WixCA"
DllEntry="CAQuietExec"
Execute="deferred"
Return="ignore"
Impersonate="no"
/>
<InstallExecuteSequence>
<Custom Action="removeW32TimeTrigger_set" After="CostFinalize" />
<Custom Action="removeW32TimeTrigger" After="InstallInitialize" />
</InstallExecuteSequence>

Can Wix3 check if a service exists?

Does Wix 3 have a built in way to just check whether a service exists? The closest guess I can come up with is using ServiceConfig and trying to detect a failure.
The AppSecInc. Community MSI Extensions has a Service_Exists custom action.
http://msiext.codeplex.com
Online Documentation:
http://code.dblock.org/Source/msiext/1.2/Docs/_custom_actions_2_system_tools_2_service_impl_8h.html#a6fdcddc7b04a310a368c08726d3be6b3
<Binary Id="SystemTools" SourceFile="$(var.BinDir)\SystemTools.dll" />
<CustomAction Id="SetServiceName" Property="SERVICE_NAME" Value="Service1" />
<CustomAction Id="ServiceExists" BinaryKey="SystemTools" DllEntry="Service_Exists" Execute="immediate" Return="check" />
<InstallExecuteSequence>
<Custom Action="SetServiceName" After="InstallFiles">NOT Installed</Custom>
<Custom Action="ServiceExists" After="SetServiceName">NOT Installed</Custom>
</InstallExecuteSequence>
SERVICE_EXISTS is set to "1" if service exists, "0" otherwise.