WIX Heat.exe command parameter -var does not accept spaces? - visual-studio-2010

I have this WIX command that uses all invariant paths and it doesn't need a system environment (unlike this example http://weblogs.sqlteam.com/mladenp/archive/2010/02/23/WiX-3-Tutorial-Generating-filedirectory-fragments-with-Heat.exe.aspx):
"%wix%bin\heat.exe" dir "$(SolutionDir)Web\obj\$(Configuration)\Package"
-cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER
-var wix.PackageSource="$(SolutionDir)Web\obj\$(Configuration)\Package"
-out "$(SolutionDir)WebInstaller\PackageFragment.wxs"
It works great, except on our build server where the solution path has a space in it and this error is thrown:
heat.exe error HEAT5057: The switch '-var' does not allow the spaces from the value. Please remove the spaces in from the value: wix.PackageSource=C:\Build\Builds 1\26e27895ae75b7cb\CADPortal\src\trunk\Web\obj\Debug\Package
I can't change the path and it shouldn't be necessary anyway in my opinion.
My question is:
How do I solve this? (I don't even get why WIX is making trouble over a quoted path/string var with a space)

To include a variable and its definition using heat, use the following mechanism.
Create an include (myinclude.wxi) file where you define your variable and its value:
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define PackageSource="c:\somePath"?>
</Include>
Create an xsl file (mytransform.xsl) for adding the <?include myinclude.wxi?> to the wxs file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="wix:Wix">
<xsl:copy>
<xsl:processing-instruction name="include">myInclude.wxi</xsl:processing-instruction>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- Identity transform. -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Run heat and specify the -t parameter that points to the transform:
heat.exe dir "c:\somePath" -cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER -var PackageSource -t mytransform.xsl -out PackageFragment.wxs
This will create the PackageFragment.wxs file as intended, add the include statement using the xsl transform, and use the variable value from the wxi file when compiling the msi (using candle later on).

For this cases you can define a prepocessor variable in the Build section of the project properties, in your case PackageSource=Web\obj\$(Configuration)\Package
and reference in the heat call like
"%wix%bin\heat.exe" dir "$(SolutionDir)Web\obj\$(Configuration)\Package"
-cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER
-var var.PackageSource
-out "$(SolutionDir)WebInstaller\PackageFragment.wxs"

The -var switch provides the name of a preprocessor variable. Something like var.Foo. Preprocessor variable names cannot contain spaces in them. The value wix.PackageSource=Whatever SolutionDir Expands To\Web\obj\Whatever Configuration Expands To\Package is not a valid name for a preprocessor variable because it has spaces in it. I expect the backslashes will be a problem as well.

So I ended up coding some build event code that inserts the necessary definition into the Heat generated file at the top, but under the starting WIX tag. Personally I'm starting to question the power of WIX if you need to do this kind of shenanigans/hacks.
Anyway this is my full build event code for anyone that needs it. (It also finds an invariant path for MSBuild.exe and creates a web package.)
echo off
set THEME_REGKEY=HKLM\Software\Microsoft\MSBuild\4.0
set THEME_REGVAL=MSBuildOverrideTasksPath
REM Check for presence of key first.
reg query %THEME_REGKEY% /v %THEME_REGVAL% 2>nul || (echo No theme name present! & exit /b 1)
REM query the value. pipe it through findstr in order to find the matching line that has the value. only grab token 3 and the remainder of the line. %%b is what we are interested in here.
set THEME_NAME=
for /f "tokens=2,*" %%a in ('reg query %THEME_REGKEY% /v %THEME_REGVAL% ^| findstr %THEME_REGVAL%') do (
set THEME_NAME=%%b
)
REM Possibly no value set
if not defined THEME_NAME (echo No theme name present! & exit /b 1)
REM replace any spaces with +
set THEME_NAME=%THEME_NAME: =+%
if errorlevel 1 goto BuildEventFailed
%THEME_NAME%MSBuild "$(SolutionDir)Web\Web.csproj" /t:Build;Package /p:Configuration=$(Configuration)
if errorlevel 1 goto BuildEventFailed
"%wix%bin\heat.exe" dir "$(SolutionDir)Web\obj\$(Configuration)\Package" -cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER -var var.PackageSource -out "$(SolutionDir)WebInstaller\PackageFragment.wxs"
REM FUNC "HeatFix" - This inserts the var. definition in top of the heat generated fragment:
MOVE /Y "$(SolutionDir)WebInstaller\PackageFragment.wxs" temp.txt
(
FOR /F "tokens=*" %%A IN (temp.txt) DO (
ECHO %%A
IF "%%A" EQU "<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">" (
ECHO ^<^?define PackageSource^=^"$(SolutionDir)Web\obj\$(Configuration)\Package^"^?^>
)
)
) > "$(SolutionDir)WebInstaller\temp.txt"
move /Y "$(SolutionDir)WebInstaller\temp.txt" "$(SolutionDir)WebInstaller\PackageFragment.wxs"
REM END FUNC "HeatFix"
goto BuildEventOK
:BuildEventFailed
echo POSTBUILDSTEP for $(ProjectName) FAILED
exit 1
:BuildEventOK
echo POSTBUILDSTEP for $(ProjectName) COMPLETED OK

I just ran into this issue and solved it by executing in the Solution Folder and using a relative path.
This works because I happen to be running heat within a PowerShell script that Jenkins is executing.
Based on the following assumptions:
Executing a PowerShell script is kosher with your build method
The PowerShell script lives inside a folder in the Solution Directory
The Question Asker's script might look like the following:
$SolutionDir = (get-item $PSScriptRoot).Parent.FullName
Set-Location $SolutionDir
& $env:WIX\bin\heat.exe" dir "$SolutionDir\Web\obj\$(Configuration)\Package"
-cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER
-var wix.PackageSource=".\Web\obj\$(Configuration)\Package"
-out "$SolutionDir\WebInstaller\PackageFragment.wxs"

Related

running exe command from another directory in a for loop

grep.exe has to be in the current directory in order for the below command to work:
FOR /F tokens^=4^ delims^=^" %%A IN ('grep.exe -o -P -m 1 "name=\"securitytoken\" value=\".*?\"" "forum_post_edit.html"') DO SET "securitytoken=%%A"
ECHO %securitytoken%
I tried enclosing the path to grep.exe between double quotes like "bin/grep.exe" but that messed up the syntax of the code.
Now I can use pushd and popd to temporarily switch to another directory where grep.exe is located but that affects forum_post_edit.html
How can I work around this problem?
Building on #Mofi's comment above, you can extract the short name of grep.exe using this command:
FOR /F %%A IN ("grep.exe") DO SET GrepPath=%%~fs$PATH:A
Now you can reference the short path in your command:
FOR /F tokens^=4^ delims^=^" %%A IN ('%GrepPath% -o -P -m 1 "name=\"securitytoken\" value=\".*?\"" "forum_post_edit.html"') DO SET "securitytoken=%%A"
ECHO %securitytoken%
Note: grep.exe would need to be in a folder defined in your system %PATH% directory in order for this to work.

Using commands inside an ANT task - escaping percentage signs?

I've got the following .bat file that uses the command line interface for Confluence to add labels to pages based on a list in a text file (Test.txt). Here are the contents of the the bat file, which works fine, and which must be run from a particular location in an Atlassian directory:
cmd /k for /F "usebackq delims=" %%A in (Test.txt) DO (confluence --action addLabels --labels Expert --space CONTROL --title "%%A")
It picks up a list of page titles in Test.txt, and then runs the Confluence command to add the label "Expert" to each of those pages.
Now I'm trying to run this as an exec task as part of an ANT build. I was successful at using an ANT exec task to just call and run the .bat file, but I want to see if I can just run the commands from inside the ANT task directly instead of even having a .bat file.
It seems to be close, but I'm getting errors around the %%A parts. Here's the ANT task I have now:
<target name="runBat">
<exec os="Windows 7" dir="C:\Developer\atlassian-cli-3.9.0" executable="cmd">
<arg value="/c"/>
<arg value="cmd /k for /F "usebackq delims=" %%A in (FAS_Test.txt) DO (confluence --action addLabels --labels Expert --space confluenceSpaceName --title "%%A")"/></exec>
</target>
Here are the latest error messages from the command line:
runBat:
[exec] Current OS is Windows 7
[exec] Executing 'cmd' with arguments:
[exec] '/c'
[exec] 'cmd /k for /F "usebackq delims=" %%A in (FAS_Test.txt) DO (confluence --action addLabels --labels Expert --space CONTROL --title "%%A")'
[exec]
[exec] The ' characters around the executable and arguments are
[exec] not part of the command.
[exec] %%A was unexpected at this time.
[exec] Result: 1
I've tried various things to escape the percentage signs but nothing works. I'm thinking that the percentage signs being doubled means that they're already escaped, but in a way that works in the .bat file but doesn't work in my ANT task. Not sure. Anyone who can help? I've tried:
%A
|%A
\%A
|%|%A
\%\%A
Okay I found this finally and it seems to work
^%A
So in the .bat file it needs to be %%A and in the ANT task command it needs to be ^%A.

Batch file in Windows to replace string in all files

I have to replace the string xmlns="http://www.wnco.com/bookingevents/v2" with nothing in all files in a windows directory
Please help me in resolving this
You can do this using powershell wrapped up in a batch file. The following is an example:
#for %%I in (%1) do #call :FIXUP "%%~fI" %2
#goto EXIT
:FIXUP
#>tmp.ps1 echo.get-content %1 ^| foreach-object { $_ -replace %2,""} ^| set-content '%~1.fixed'
#set curdir=%~dp0
#set script=%curdir%tmp.ps1
#powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%script%'"
#del >NUL /q %1 && #ren %1.fixed %1
#exit /b 0
:EXIT
The powershell script is created on the fly here and expects to see ASCII files. If you use unicode, check the "-Encoding Unicode" option for the powershell command.
So given a sample xml file as shown below we can use fixup.cmd *.xml "xmlns=""http://www.wnco.com/bookingevents/v2""" to apply this match expression to all the xml files in the current directory. I tested this using the following in some files:
<?xml version=1.0 encoding="UTF-8"?>
<a xmlns="http://www.wnco.com/bookingevents/v2">
<b>Something</b>
</a>
Running this:
C:\temp\demo>dir
27/02/2014 10:33 116 a.xml
27/02/2014 10:33 116 b.xml
27/02/2014 10:33 116 c 1.xml
27/02/2014 10:59 354 fixup.cmd
C:\temp\demo>fixup *.xml "xmlns=""http://www.wnco.com/bookingevents/v2"""
C:\temp\demo>type "c 1.xml"
<?xml version=1.0 encoding="UTF-8"?>
<a >
<b>Something</b>
</a>
The main issue to be careful with is the quoting to handle spaces in filenames.

Windows, runnig script with variable paramameter, for cycle

There is a list of text files that needs to be processed. Their names are
samples\file_1.txt
...
samples\file_100.txt
A script calls file test.exe with 2 parameters. The first and second parameters represent input and output file names, which change with the increment 1, and the parameter -t is fixed:
test.exe \input\file_1.txt \output\file_1.txt -t
...
test.exe \input\file_100.txt \output\file_100.txt -t
How to write a simplified version of the script, processing files one by one, using the for cycle?
I solved this problem using the Python script, but hope, there is a more common way...
#echo off
cd /d "c:\samples\input"
md "..\output" 2>nul
for %%a in (*.txt) do (
test.exe "%%a" "..\output\%%a" -t
)

Modifying WMAppManifest.xml based on Build Configuration

I would like to release to distinct flavours of my app and would like to indicate this in the application name displayed on the phone. As far as I know for Silverlight Phone Apps the name is solely determined by WMAppManifest.xml. Therefore I would like to modify the application title at build time based on my Build Configuration. Any suggestions?
You can do this with a bit of T4 templating and code generation (see http://msdn.microsoft.com/en-us/library/bb126445.aspx if you don't know about this.)
The following steps will allow you to use a different application title if you are using the debug or release configuration.
Take a copy of WMAppManifest.xml and rename it to WMAppManifest-base.tt
Change the content of WMAppManifest-base.tt to be
<## template language="C#" #><## output extension=".xml" #><?xml version="1.0"?>
<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.0">
<App xmlns="" ProductID="{4c5315b6-4030-46c5-b5ea-17284d6af0c6}" Title="<#= this.ConfiguredAppTitle #>" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal" Author="WindowsPhoneApplication8 author" Description="Sample description" Publisher="WindowsPhoneApplication8">
<IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
<Capabilities>
<Capability Name="ID_CAP_IDENTITY_DEVICE"/>
<Capability Name="ID_CAP_NETWORKING"/>
</Capabilities>
<Tasks>
<DefaultTask Name ="_default" NavigationPage="MainPage.xaml"/>
</Tasks>
<Tokens>
<PrimaryToken TokenID="WindowsPhoneApplication8Token" TaskName="_default">
<TemplateType5>
<BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
<Count>0</Count>
<Title><#= this.ConfiguredAppTitle #></Title>
</TemplateType5>
</PrimaryToken>
</Tokens>
</App>
</Deployment>
<#+
string ConfiguredAppTitle = "MyPhoneApp";
#>
(Adjust capabilities, etc. as appropriate.)
In the same folder as WMAppManifest-base.tt create a file called Debug.WMAppManifest.tt with the following contents:
<#
ConfiguredAppTitle = "MyDebugApp";
#><## include file="WMAppManifest-base.tt" #>
Now create a file called Release.WMAppManifest.tt with the following contents:
<#
ConfiguredAppTitle = "MyReleaseApp";
#><## include file="WMAppManifest-base.tt" #>
Create a file called copyifnewer.bat in the root of the project. Give it the following contents:
echo Comparing: %1 with %2
if not exist %1 goto File1NotFound
if not exist %2 goto File2NotFound
fc %1 %2
if %ERRORLEVEL%==0 GOTO NoCopy
echo Files are not the same. Copying %1 over %2
copy %1 %2 /y & goto END
:NoCopy
echo Files are the same. Did nothing
goto END
:File1NotFound
echo %1 not found.
goto END
:File2NotFound
copy %1 %2 /y
goto END
:END
In the project properties add this PRE-build command:
"$(ProjectDir)\copyifnewer.bat" "$(ProjectDir)properties\$(ConfigurationName).WMAppManifest.xml" "$(ProjectDir)properties\WMAppManifest.xml"
Now you can adjust the values in the debug & release files to alter the titles as you wish.
If you have other configurations just create appropriately named files (with the same contents as the debug.*.tt) and they'll be picked up automatically.
Note that when testing, if you install the app with one name (in the emulator or phone) you'll have to uninstall it to see a name change reflected in the application list.
Note to self: must blog about this. (It's really powerful but hard to work out how to do the first time.)
You can use the pre-build step (Project Properties -> Build Events -> Pre-Build event command line) in the project properties with conditional command-lines to achieve this.
Having files for each version and then copying over the default to replace the data there. You can also set up your icons to use this same system! :)
if $(ConfigurationName) == Phone_Free_Debug (
copy /Y $(ProjectDir)Properties\WMAppManifest_Free.xml $(ProjectDir)Properties\WMAppManifest.xml
copy /Y $(ProjectDir)173x173icon_Free.png $(ProjectDir)173x173icon.png
copy /Y $(ProjectDir)200x200icon_Free.png $(ProjectDir)200x200icon.png
)
if $(ConfigurationName) == Phone_Free_Release (
copy /Y $(ProjectDir)Properties\WMAppManifest_Free.xml $(ProjectDir)Properties\WMAppManifest.xml
copy /Y $(ProjectDir)173x173icon_Free.png $(ProjectDir)173x173icon.png
copy /Y $(ProjectDir)200x200icon_Free.png $(ProjectDir)200x200icon.png
)
if $(ConfigurationName) == Phone_Debug (
copy /Y $(ProjectDir)Properties\WMAppManifest_Paid.xml $(ProjectDir)Properties\WMAppManifest.xml
copy /Y $(ProjectDir)173x173icon_Paid.png $(ProjectDir)173x173icon.png
copy /Y $(ProjectDir)200x200icon_Paid.png $(ProjectDir)200x200icon.png
)
if $(ConfigurationName) == Phone_Release (
copy /Y $(ProjectDir)Properties\WMAppManifest_Paid.xml $(ProjectDir)Properties\WMAppManifest.xml
copy /Y $(ProjectDir)173x173icon_Paid.png $(ProjectDir)173x173icon.png
copy /Y $(ProjectDir)200x200icon_Paid.png $(ProjectDir)200x200icon.png
)
I know this is an old thread now, but came across it as I was looking to do something similar and the accepted answer was spot on for me. I just wanted to add in a suggestion for a modification to the batch file part of it, in case it helps anyone else. I would have commented on the accepted answer but lack the reputation to so far, so hope no-one minds me adding it as a separate answer..!
If you're version-controlling your solution (e.g. in TFS) your WMAppManifest might be write-protected, unless you specifically remembered to check it out prior to build. If it is, the batch file won't be able to overwrite it, but you won't get any notification of it and the build will proceed, meaning you may fail to notice it didn't update per your build configuration. To address this, add the following to the end of the batch file (after :END):
if %errorlevel% neq 0 exit /b %errorlevel%
If the batch file fails, the build will stop, alerting you to the issue.
Also, don't create the batch file in Visual Studio! It'll save in UTF-8 encoding (I think) and you may get errors when Windows tries to shell it. Create it in notepad to ensure it saves in ASCII. If you save it with the wrong encoding and it won't run, the above amend will catch that as well.

Resources