WIX installer: How to create environment variable from vswhere.exe output - batch-file

I have a window installer XML (WIX) installer.
I use Visual Studio vswhere.exe to find out Visual Studio InstallationPath and InstanceId for further usage in my WIX code, see below:
<SetProperty Id="InstallationPathProf2019"
Value=""[SystemFolder]cmd.exe" /C ""[VS_INSTALLER_DIR_PATH]\vswhere.exe" -products Microsoft.VisualStudio.Product.Professional -property InstallationPath -version [\[]16.0,17.0[\)] > [TempFolder]InstallationPathProf2019.txt" && set /P VS_2019_INSTALLATIONPATH=<[TempFolder]InstallationPathProf2019.txt"
Before="InstallationPathProf2019" Sequence="execute" >NOT REMOVE="ALL"</SetProperty>
<CustomAction Id="InstallationPathProf2019" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no"/>
<SetProperty Id="InstanceIdProf2019"
Value=""[SystemFolder]cmd.exe" /C ""[VS_INSTALLER_DIR_PATH]vswhere.exe" -products Microsoft.VisualStudio.Product.Professional -property InstanceId -version [\[]16.0,17.0[\)] > [TempFolder]InstanceIdProf2019.txt && set /P VS_2019_INSTANCEID=<[TempFolder]InstanceIdProf2019.txt""
Before="InstanceIdProf2019" Sequence="execute" >NOT REMOVE="ALL"</SetProperty>
<CustomAction Id="InstanceIdProf2019" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no"/>
in addition, I use set directory with the value retrieved from vswhere as following:
<SetDirectory Id="APPLICATIONROOTDIRECTORY" Value="[%VS_2019_INSTALLATIONPATH]\Test" Sequence="execute" />
Which doesn't work:
return empty string - MSI (c) (28:F8) [12:15:12:623]: PROPERTY CHANGE: Adding APPLICATIONROOTDIRECTORY property. Its value is '\Test'.
When I use predefined environment variable it work:
<SetDirectory Id="USERPROFILDIR" Value="[%USERPROFILE]\MyTest" Sequence="first" />
I see in log file:
PROPERTY CHANGE: Adding USERPROFILDIR property. Its value is 'C:\Users\donnam\MyTest'.
Why in my case it returns empty string? can't I set a value of environment variable and use it afterwards on my WIX code?
P.S. performing the above in CMD (not through WIX) I see environment variable is created:
set environment from vswhere output
I hope someone can help!

The solution is to use setx to create environment variable instead of set.
More information about setx can be found at:
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setx

Related

How to specify username/ password when using aws-adfs in command prompt

In Linux, the following commands work just fine where we specify a username and password to the envrioment variables and have them used in aws-adfs using '--env" switch
Linux .sh
export username=<my_username>
export password=<my_pass>
aws-adfs login --region <region> --adfs-host <adfs-host> --role-arn <Role> --env --no-sspi --profile <local-profile-name>
On Windows using cmd, I can't get the "--env" to work. Regardless of what I've tried here, it will always use my AD logged-in user and not the user/password of my second account which I want to use.
Windows:
(aws-adfs) C:\>set username="my_username"
(aws-adfs) C:\>set password="my_password"
aws-adfs login --region <region> --adfs-host <adfs-host> --role-arn <Role> --env --profile <local-profile-name>
Powershell:
$env:username = 'my_username'
$env:password = 'my_password'
dir env:
aws-adfs login --region <region> --adfs-host <adfs-host> --role-arn <Role> --env --profile <local-profile-name>
Does anyone know how to get one of the following working in cmd while using aws-adfs?
--env Read username, password from environment
variables (username and password).
--stdin Read username, password from standard input
separated by a newline.
--authfile TEXT Read username, password from a local file
(optional)
UPDATE:
from the comments below, the values for setting the username and password in a batch file work.
set "username=my_username"
set "password=my_password"
However, we appear to have a bug with aws-adfs where you are also required to use the full email address for the username on windows but in Linux, just the username works
There is predefined on Windows the environment variable USERNAME with all letters in upper case which holds the name of the user account, see the Wikipedia chapter Windows Environment Variables for more information about the predefined environment variables on Windows.
aws-adfs of version 2.0.1 does not require on Windows that the environment variable username is defined with all letters in lower case. Environment variable names are case-sensitive on Linux where it is possible to define an environment variable USERNAME and additionally also an environment variable username.
There could be used in a Windows command prompt window:
set "username="
set "username=my_username#mail.com"
set "password="
set "password=my_password"
There is deleted first case-insensitive the environment variable USERNAME and next defined the environment variable username with all letters in lower case with the string between the equal sign and the double quote character at the end. The redefinition of an existing environment variable does not change the case of the letters of the variable name. The user name being usually an email address should be defined completely.
Then is deleted case-insensitive the environment variable password being perhaps defined by chance and redefined with name password with all letters in lower case with the string value between = and " at end of the command line.
Please read my answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? It makes a big difference for the Windows command set on first " being at beginning of the argument string of command SET left to the variable name or at beginning of the string value after the equal sign. set username="my_username" assigns the string "my_username" with both double quotes and with by mistake entered trailing whitespaces to the environment variable username which would be definitely not good in this use case. A user name string with " is definitely not working.
But it is also possible in a Windows command prompt window using just:
set "username=my_username#mail.com"
set "password=my_password"
The string value of the environment variable USERNAME is in this case updated with the string my_username#mail.com. The variable name is not changed although writing the name completely in lower case in the command prompt window.
In a PowerShell console window should be done the same with:
$Env:username = ''
$Env:username = 'my_username#mail.com'
$Env:password = ''
$Env:password = 'my_password'
Or there is used just:
$Env:username = 'my_username#mail.com'
$Env:password = 'my_password'
See the Microsoft documentation about Environment Variables - PowerShell.
The modifications made on the environment variables list are the same as done with command SET in the Windows command prompt window.
In would be good to define the two environment variables in a Windows batch file in a separate local execution environment as done with:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "username=my_username#mail.com"
set "password=my_password"
aws-adfs login --region "region" --adfs-host "adfs-host" --role-arn "Role" --env --profile "local-profile-name"
endlocal
The command SETLOCAL creates a new environment variables list as a copy of the current environment variables list. In this new list the environment variables USERNAME and (if exiting at all) password are updated or created new with the correct string values. Then aws-adfs is executed using the two environment variables. Finally the command ENDLOCAL discards the just used list of environment variables with the variables USERNAME and password and restores the previous environment variables list with the variable USERNAME with the string value as defined by default for the current account by the Windows shell.
Read this answer for details about the commands SETLOCAL and ENDLOCAL.

how to concatenate two variables when one has spaces in it in AzureDevops batch file

I have this input in my ps script. I am passing $(Build.ArtifactStagingDirectory) system variable to get the path in the input %1. The path has spaces i.e. E:\Build Agents\Agent2\_work\15\a. I need to concatenate this with other path but I am not able to do so.
set BuildDrop=%1
set Directory=%BuildDrop% + "\adapters\bin"
This is my output, which is incorrect as Directory should be something like E:\Build Agents\Agent2\_work\15\a\adapters\bin. How to solve this?
set BuildDrop="E:\Build Agents\Agent2\_work\15\a"
set Directory="E:\Build Agents\Agent2\_work\15\a" + "\adapters\bin"
My task is like this in my build pipeline
Task : Batch script
Description : Run a Windows command or batch script and optionally allow it to change the environment
Version : 1.1.10
I found the solution for this. Since my %1 had space I needed to remove apostrohphe before assigning. I did it using ~ variable. working code looks like this.
set "BuildDrop=%~1"
set "Directory=%BuildDrop%\adapters\bin"
You could try this :
set BuildDrop=%1
set Directory= %BuildDrop%\adapters\bin
echo %Directory%
Sample Output
This is the concatenation code.
%BuildDrop%\adapters\bin
In my above sample I am trying to concatenate C:\Users\svijay\Desktop & \adapters\bin using the batch script
And the output : C:\Users\svijay\Desktop\adapters\bin
UPDATE :
#echo off
set BuildDrop=%1
set Directory= %BuildDrop%\adapters\bin
set Directory= %Directory:"=%
echo %Directory%
If you have space, you could provide the "" to provide input.
In your code you could remove the double quotes.
%Directory:"=% - Removes the Double quotes from the string literal.
Sample output :
You may use this to create Azure DevOps variable containing your path
powershell: |
$directory = $(Build.ArtifactStagingDirectory)
$newDirectory = $directory + "\adapters\bin"
Write-Host "##vso[task.setvariable variable=testvar]$newDirectory "
and if you need set variable in powershell script
$BuildDrop=$args[0]
$Directory=$BuildDrop + "\adapters\bin"
Here you have an article how to use parameters in powershell.
We can use powershell task in the Azure DevOps to concatenate two variables.
Sample:
Write-Host "Build.ArtifactStagingDirectory is $(Build.ArtifactStagingDirectory)"
Write-Host "Build.ArtifactStagingDirectory is $(Build.ArtifactStagingDirectory)\adapters\bin"
Result:
In addition, I found a similar issue, please also check it.
Update1
Use power shell script in the Azure DevOps pipeline
$testpath1 = "E:\Build Agents\Agent2\_work\15\a"
$testpath2 = "\adapters\bin"
Write-Host "path is $($testpath1)$($testpath2)"
Result:
Use local power shell
Result:
Update2
.bat file
set testpath1=E:\Build Agents\Agent2\_work\15\a
set testpath2=\adapters\bin
set newpath=%testpath1%%testpath2%
echo %newpath%
Pipeline result:

Running a build script after calling vcvarsall.bat from powershell

I am attempting to run the Visual Studio (developer cmd prompt) environmental variable setup batch file followed by a build script from within a Powershell script as follows:
cmd /v:on/k "(""C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"" amd64_x86 && C:\buildscript.cmd --build-options)"
It appears however that the environmental settings established by vcvarsall.bat are not retained for the build script call. i.e. no default compiler setup, etc.
Is the /v:on combined with the /k switch not actually utilizing the same cmd session and properly delaying environmental variable expansion? Perhaps the approach is wrong ...
The problem is that when you run cmd.exe to run a batch file, the variables are set in that instance of cmd.exe, but they disappear after that instance terminates.
To work around this problem, you can use the Invoke-CmdScript function in this article:
Windows IT Pro: Take Charge of Environment Variables in PowerShell
The function is as follows:
# Invokes a Cmd.exe shell script and updates the environment.
function Invoke-CmdScript {
param(
[String] $scriptName
)
$cmdLine = """$scriptName"" $args & set"
& $Env:SystemRoot\system32\cmd.exe /c $cmdLine |
select-string '^([^=]*)=(.*)$' | foreach-object {
$varName = $_.Matches[0].Groups[1].Value
$varValue = $_.Matches[0].Groups[2].Value
set-item Env:$varName $varValue
}
}
You could add this function to your PowerShell profile or use it as a script file.
Once you have defined the function, you can run your commands:
Invoke-CmdScript "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64_x86
C:\buildscript.cmd --build-options
...or whatever you need.
The article also presents a couple of functions that let you easily save and restore environment variables.

TortoiseSvn - Automatically update multiple external properties

I'm currently working on tortoise svn. In order to be able to automatically tag trunk projects so i need to focus on the external properties. As well i would like to edit them automatically using a batch file.
Until now what i've done is:
Getting the last version of the folder which is pointed by the
external property (in order to be able to tag a specific version and
not the head one)
Edit the external property using command line
My batch file looks like this :
::GETTING THE LAST VERSION NUMBER OF THE SOURCE DIRECTORY
svnversion -c %SRC_PATH_WC% | sed -e 's/[MS]//g' -e 's/^[[:digit:]]*://'>temp.txt
set /p VERSION=<temp.txt
del temp.txt
echo %VERSION%
pause
::CREATING THE SVN:EXTERNAL WITH THE VERSION CHOOSEN
svn propset svn:externals "%DIRECTORY_NAME% -r%VERSION% %SVN_SRC_PATH%" .
pause
Now I would like to be able to set multiple external properties. I think i can't by using the svn propset command but i have no clue on what other command to use and how to use it.
Thank you in advance for your help
I've found my answer on another site.
Here is what i've used :
::CREATE FILE AND WRITE THE SVN:EXTERNALS PROPERTIES
echo %DIRECTORY_NAME1% -r%VERSION1% %SVN_SRC_PATH1% > svn_externals
echo %DIRECTORY_NAME2% -r%VERSION2% %SVN_SRC_PATH2% >> svn_externals
::CREATING THE SVN:EXTERNAL WITH THE VERSION CHOOSEN
svn propset svn:externals -F svn_externals .

How to convert this sqlcmd script to an Invoke-Sqlcmd script?

I have a SQL script designed to be executed by sqlcmd, and a Command script that executes sqlcmd with the correct parameters.
I want to convert the Command script to a PowerShell script that uses Invoke-Sqlcmd instead of sqlcmd.
The SQL script, the Command script, and the new PowerShell script all live in the directory C:\Users\iain.CORP\SqlcmdQuestion.
SQL Script
The SQL script is called ExampleQuery.sql. It selects a string literal. The value of the string literal is set by sqlcmd at runtime to the value of the ComputerName sqlcmd scripting variable. The code looks like this:
SELECT '$(ComputerName)';
Command Script
The command script is called ExecQuery.cmd. It calls sqlcmd to execute ExampleQuery.sql and sets the value of the scripting variable ComputerName to the value of the environment variable COMPUTERNAME. The code looks like this:
sqlcmd -i ExampleQuery.sql -v ComputerName = %COMPUTERNAME%
When I open a command prompt, the default working directory is C:\Users\iain.CORP. I change the to the directory containing the files, and run the Command script:
cd C:\Users\iain.CORP\SqlcmdQuestion
ExecQuery.cmd
I see this output:
---------
SKYPC0083
(1 rows affected)
The script successfully selects a string literal set by sqlcmd.
PowerShell Script
The PowerShell script is called ExecQuery.ps1. It is supposed to do the same as the command script, using Invoke-Sqlcmd instead of sqlcmd. The code looks like this:
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
When I open a PowerShell prompt, the default working directory is Z:\. I change to the directory containing the files, and run the PowerShell script:
cd C:\Users\iain.CORP\SqlcmdQuestion
.\ExecQuery.ps1
I see this output:
Invoke-Sqlcmd : Could not find file 'Z:\ExampleQuery.sql'.
At C:\Users\iain.CORP\SqlcmdQuestion\ExecQuery.ps1:4 char:14
+ Invoke-Sqlcmd <<<< -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
+ CategoryInfo : InvalidResult: (:) [Invoke-Sqlcmd], FileNotFoundException
+ FullyQualifiedErrorId : ExecutionFailed,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
The PowerShell script raises an error because Invoke-Sqlcmd can't find the the input file in the Z:\ directory, which happens to be the default working directory.
The Command script found the script in the current working directory.
How do I make Invoke-Sqlcmd use the current working directory instead of the default working directory?
For this answer, assume that the directory C:\Users\iain.CORP\SqlcmdQuestion exists and that executing dir at that location produces the following output, as implied by the question:
Directory: C:\Users\iain.Corp\SqlcmdQuestion
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 26/09/2012 15:30 27 ExampleQuery.sql
-a--- 26/09/2012 15:30 61 ExecQuery.cmd
-a--- 26/09/2012 15:34 172 ExecQuery.ps1
PowerShell ignores the working directory by design
My question has a false premise:
How do I make Invoke-Sqlcmd use the current working directory instead of the default working directory?
The cmdlet does use the current working directory. The problem is that I didn't change the working directory at all in my PowerShell session.
In PowerShell, cd is an alias for the Set-Location cmdlet. You can prove this using the Get-Alias cmdlet:
Get-Alias cd
Output:
CommandType Name Definition
----------- ---- ----------
Alias cd Set-Location
Alex Angelopoulos explains:
[A]lthough PowerShell's location is analogous to the working directory, the location is not the same thing as the working directory. In fact, PowerShell doesn't touch the working directory.
Set-Location does not set the working directory. It sets the working location, which is a similar but distinct concept in PowerShell.
You can prove this by inspecting the working directory using the .NET property Environment.CurrentDirectory after setting the working location using cd as in the question:
cd C:\Users\iain.CORP\SqlcmdQuestion
Environment::CurrentDirectory
Output:
Z:\
I would guess this design decision was made to be consistent. The working directory would be undefined when, for example, the working location were set to a registry hive.
Invoke-Sqlcmd violates this design principle
Invoke-Sqlcmd violates PowerShell's general design principle to use the working location rather than the working directory. Most cmdlets use the working location to resolve relative paths, but Invoke-Sqlcmd is an exception.
Using the ILSpy disassembler and a little intuition to inspect the containing assembly Microsoft.SqlServer.Management.PSSnapins, I believe I have found the reason for the error.
I believe that the cmdlet's parameter -InputFile is implemented by the method IncludeFileName. ILSpy's disassembly of the method looks like this:
// Microsoft.SqlServer.Management.PowerShell.ExecutionProcessor
public ParserAction IncludeFileName(string fileName, ref IBatchSource pIBatchSource)
{
if (!File.Exists(fileName))
{
ExecutionProcessor.sqlCmdCmdLet.TerminateCmdLet(new FileNotFoundException(PowerShellStrings.CannotFindPath(fileName), fileName), "ExecutionFailureException", ErrorCategory.ParserError);
return ParserAction.Abort;
}
BatchSourceFile batchSourceFile = new BatchSourceFile(fileName);
pIBatchSource = batchSourceFile;
return ParserAction.Continue;
}
Invoke-Sqlcmd uses the .NET method File.Exists to check whether the specified input file exists. The method's documentation remarks that relative paths are resolved using the working directory:
The path parameter is permitted to specify relative or absolute path
information. Relative path information is interpreted as relative to
the current working directory. To obtain the current working
directory, see GetCurrentDirectory.
This suggests that File.Exists would return false in this case, which would cause the error message seen in the question. You can prove this by executing the method directly from the prompt:
cd C:\Users\iain.CORP\SqlcmdQuestion
[IO.File]::Exists('ExecQuery.sql')
Output:
False
The method returns false, so the cmdlet terminates with a 'file not found' error.
You can work around the unusual behavior
There are two workarounds for Invoke-Sqlcmd using the working directory instead of the working location to resolve relative paths:
Always use an absolute path as the value of the -InputFile parameter. CandiedCode's answer shows how to do this.
Set the working directory and use a relative path.
I solved the problem without side-effects by modifying ExecQuery.ps1 like this:
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
$RestoreValue = [Environment]::CurrentDirectory
[Environment]::CurrentDirectory = Get-Location
Invoke-Sqlcmd -InputFile 'ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
[Environment]::CurrentDirectory = $RestoreValue
I see this output:
Column1
-------
SKYPC0083
Success!
The new script sets the working directory to match the working location before executing Invoke-Sqlcmd. To avoid unintended side-effects of changing the working directory, the scrtipt restores the working directory value before completing.
Setting the current directory is described in this Channel 9 thread. The example there uses the Directory.SetCurrentDirectory method, but I find it simpler to set the property directly.
You could fully qualify the Inputfile location:
Invoke-Sqlcmd -InputFile 'C:\Users\iain.CORP\SqlcmdQuestion\ExampleQuery.sql' -Variable "ComputerName = $Env:COMPUTERNAME"
And use a variable to drive the script location:
$FileLocation = 'C:\Users\iain.CORP\SqlcmdQuestion\'

Resources