TFS 2013 - Update MSBuild Parameters from pre-build PowerShell script - powershell

I have recently upgraded from TFS 2012 to TFS 2013 and am trying to use the new template (TfvcTemplate.12.xaml). I need to set the version numbers of my .NET applications and WiX installers as part of this process.
In my TFS 2012 process I customised the build template using TFS Community Build Extensions to do the following:
Generate a version number. The Major and Minor version numbers are static, the release number is the number of days since 2014-11-25, the build number is the number of times the build definition has run today.
Update all AssemblyInfo.cs files with the new version number
Update the MsBuildArguments argument to pass the version as a parameter. This is so that I can set the version number in my WiX installers.
Before I resort to customising the build template again I would like to try to achieve the above using a Pre-build PowerShell script.
Items 1 and 2 were easy in PowerShell but I am stuck with the third requirement. Is it possible for the Pre-build PowerShell script to update the MSBuildArguments?

I think there is a simpler way to do this. This is what we did.
Created an include file called Version.wsi. The file contains these 4 lines:
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define CurrVersion="1.0.0.0" ?>
</Include>
In our wxs file, right after the line
<Wix xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns="http://schemas.microsoft.com/wix/2006/wi" >
add the following:
<?include Version.wxi ?>
In your wxs file use the variable $(var.CurrVersion) where you currently specify your version.
Now all you need to include in your powershell script is some code to modify the wxi file with the correct version number. This is how I do it:
function UpdateVersion($newVersion)
{
$wxiFile = gci "$Somedir\Version.wxi"
#clear the read-only bit just in case
Set-ItemProperty $wxiFile -name IsReadOnly -value $false -Force
$newContent = $content -replace "1.0.0", $prodVersion
Set-Content -Path $wxiFile -Value $newContent
}

Related

Version of jar file by reading its manifest using powershell

I am running a powershell script to idenfity the versions. For DLL's and EXE's using the following function to get the version . I have a few other files with the extension .Jar. Is there a way I can use powershell to open the jar and get the version of it from their manifest.
Please let me know.
#{n='Version';e={$_.versioninfo.Fileversion}}
Looks like you have to extract from the jar file first. I downloaded java and tested myself using a jar file I also downloaded
& "C:\Program Files\Java\jdk1.8.0_191\bin\jar.exe" xvf junit-4.10.jar META-INF/MANIFEST.MF
get-content .\META-INF\MANIFEST.MF
RESULTS
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.2
Created-By: 1.6.0_26-b03-384-10M3425 (Apple Inc.)
That being said, please read here, Do we want single, complete answers? where Implementation-Version is mentioned, so make sure you know where the version is to be and if you can depend on this.
This works as a PowerShell script on Server 2016/PS 5.1 and Win 10/PS 7.1 without installing anything extra. It reads the MANIFEST.MF file in the zip archive and writes to standard output. This is option 4 found here: https://stackoverflow.com/a/37561878/101151
Be sure to pass the absolute path to [io.compression.zipfile]::OpenRead. It seems to bind to the first directory it was run in and re-use that for relative paths.
# Read the MANIFEST.MF from a Java .JAR (really a .zip) file and output to standard output
param(
[Parameter(Mandatory=$true)][string]$jarname
)
# The following code is based on an answer at https://stackoverflow.com/a/37561878/101151
Add-Type -assembly "system.io.compression.filesystem"
$zip = [io.compression.zipfile]::OpenRead((Get-ChildItem $jarname).FullName)
$file = $zip.Entries | where-object { $_.Name -eq "MANIFEST.MF"}
$stream = $file.Open()
$reader = New-Object IO.StreamReader($stream)
$text = $reader.ReadToEnd()
$text
$reader.Close()
$stream.Close()
$zip.Dispose()

Determining Installed Visual Studio Path for 2017

I have a powershell script that looks at a list of VS installations, and determines the highest version installed. It then uses the InstallDir for that version, and uses it to access various commands.
It still uses the lower versions, however.
As of VS2017, it appears that the Registry keys are no longer saved in the same way. I need to update the script to be able to figure out the 2017 settings.
#Add New Versions to this list when new versions of VS are released
$VsVersionsToDisable = "10.0", "11.0", "12.0", "14.0"
[System.Collections.ArrayList]$VsVersions = $VsVersionsToDisable
#Find the Highest installed VS Version, and use it for the TFS.exe Command.
foreach ($version in $VsVersions | Sort-Object -Descending)
{
$keyPath = "HKCU:\Software\Microsoft\VisualStudio\$version`_Config"
If (Test-Path $keyPath)
{
$aliasPath = Get-ItemProperty -Path $keyPath | Select-Object `
-ExpandProperty InstallDir
$proxyPath = Join-Path $aliasPath "tf.exe"
set-alias proxyTF $proxyPath
}
}
To avoid an XY question: We use this script to configure the TFS Proxy settings for a user. It determines the highest installed version, uses it to find the proxy, then iterates through the lower versions configuring their proxy settings with the same value.
What is the best way to determine the installation directory (and also the tf.exe location) for VS2017?
From what I can see, use the SxS\VS7 option:
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7
It should give you the root paths to Visual Studio:
That should get you going.
The tf.exe location is then stored using a symlink under:
.\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\tf.exe
Since you're using PowerShell, check out https://github.com/microsoft/vssetup.powershell, which is a PS module for detecting installations of VS2017+.
Otherwise, you could need to rely on the Nuget package which is the supported means of detecting VS.
See also this answer on a related question, which predates the PS module I listed above but contains some unsupported methods for finding VS.
I did use this as a reference and came to a solution in another way.
I'm not sure how resilient it is with regards to other versions, but it did the trick for me. It get's the directory of devenv and then I add the extra on the end for TFS. Obviously if the structure is different, then we are screwed.
Hope it helps.
$regKey = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\devenv.exe"
$visualStudioDir = Get-ItemPropertyValue -Path $regKey -Name "(Default)"
$visualStudioDir = ($visualStudioDir.Replace("devenv.exe","")).replace("`"","")
$tfsPath = 'CommonExtensions\Microsoft\TeamFoundation\Team Explorer\tf.exe'
Set-Alias tf $visualStudioDir$tfsPath
tf workspaces

Can MSBUILD receive NEW 'user' level environment variables without restarting Visual Studio?

I have a NuGet package that sets up some PowerShell cmdlets in its Init.ps1 file, and one of the things I'd like them to be able to do is set environment variables that are passed to a build in Visual Studio.
In my Init.ps1 script I use the line:
[Environment]::SetEnvironmentVariable("MyVariable", $someValue, "User")
...to set a 'User' level environment variable, figuring that a regular 'Process' level variable won't work since Package Manager Console is in a different process than MSBuild. Also, manually setting $env:MyVariable = "foo" in Package Manager Console does not pass its value to MSBuild.
In MSBuild, a regular $(MyVariable) is not populated with 'foo' as desired.
If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.
The goal is to be able to drop to Package Manager Console, run an arbitrary cmdlet and have the changes persisted in properties during build. Answers that require reboot, restart or reloading a solution aren't what I'm looking for.
Am I missing something about environment variables?
Is there another simple way to set build properties from Package Manager Console that I've overlooked (short of using EnvDTE or Microsoft.Build to manually edit each project's csproj file?
Update - some further discoveries:
The environment variables are set correctly, and I can echo them back from command prompt too.
If I restart Visual Studio completely then the variable finally reaches MSBUILD, but then subsequent changes to the variable aren't picked up.
Seems like Visual Studio is caching the environment variable. Is there a way to 'refresh' a process' environment variables?
If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.
Are you sure?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foo">
<Exec Command="setx MyVariable Foo" />
<Exec Command="echo 1. %MyVariable%" />
<Exec Command="echo 2. $(MyVariable)" />
<PropertyGroup>
<MyVariable>$([System.Environment]::GetEnvironmentVariable('MyVariable', System.EnvironmentVariableTarget.User))</MyVariable>
</PropertyGroup>
<Message Text="3. $(MyVariable)" />
</Target>
</Project>
At the end of the day, no, I couldn't find a way to get the Environment variables without a restart.
In the end I solved this by using a separate Properties.targets (arbitrary name) to store my 'variables' like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Storage for global configuration properties -->
<PropertyGroup>
<MyVariable1></MyVariable1>
<MyVariable2></MyVariable2>
</PropertyGroup>
</Project>
and importing that into my build script with the following line:
<Import Project="Properties.targets" />
Then, to manipulate the variables I use two powershell functions, passing it $toolsPath from Init.ps1, and using this to set properties:
function SetPackageProperty($toolsPath, $name, $value) {
$propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
$msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
$var = $msbuild.Xml.AddProperty($name, $value)
$msbuild.Save()
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
}
And to get properties:
function GetPackageProperty($toolsPath, $name) {
$msbuild = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
$msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
$var = $msbuild.Xml.Properties | Where-Object {$_.Name -eq $name} | Select-Object -First 1
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
return $var.Value
}
You can import Properties.targets into any build script you want to use the properties.
Hope this helps somebody!

Powershell get latest on a working folder?

I am trying to get latest on a specific folder and was wondering how I would do this. I have been reading the MSDN documentation about the Workspace class but unfortunately it doesn't seem to provide any information about how I would go about getting latest of a specific folder.
For example, I have a single workspace but with multiple working folders. My PowerShell script can get latest but only at workspace level. Is it possible to get it from a working directory level or at a particular folder level?
Thanks in advance, DS.
EDIT
I believe this is possible as power tools is able to do it via right clicking a folder and getting latest. Ideally, want to replicate this.
http://msdn.microsoft.com/en-us/library/Microsoft.TeamFoundation.VersionControl.Client.Workspace.aspx
The below shows my script in practice..
$tfs = .\tfs-get-instance.ps1 -uri $uri
$vcs = $tfs.TfsTeamProjectCollection.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])
[psobject]$workspace = $vcs.GetWorkspace([System.Environment]::MachineName, [System.Environment]::UserName)
$workspace.Map($sourceFolder, $localFolder)
$result = $workspace.GetLocalItemForServerItem($sourceFolder);
if ($result -ne "")
{
echo $result
}
You should be able to use the Workspace.Get Method (String[], VersionSpec, RecursionType, GetOptions) to get what you want.
On the other hand, if you don't necessarily want to do it using the .NET objects, you could let tf.exe do the actual work, like the following:
& $TfExePath workfold /map "$codePath" "$LocalFolderPath" /collection:"$tfCollection" /workspace:$workspaceName
& $TfExePath get "$codePath" /version:$ChangeSet /force /overwrite /all /recursive $workspaceName
On the other hand, if you have the TF PowerTools installed you should also have PowerShell cmdlets for working with TFS. I have never used them, but I'm guessing you should be able to use them instead of using tf.exe if you want to. I would probably go for the PowerShell cmdlets in the power tools if they fill your need.

Get Latest Version of Folder from TFS, using Powershell

I am trying to "Get Latest Version" of a particular folder from TFS, using Powershell.
I have installed the TFS Snappin, and have been using TFS Power Tools cmdlets in PowerShell (such as Get-TfsChildItem and Select-TfsItem etc) [How do I set up TFS PowerShell Snapin ], and have gone through their documentation (which I didn't find explanatory enough!).
Confused, on the exact cmdlet to use, when I am trying to get the latest version of an entire Folder structure from TFS, that is mapped to my local drive (and not just a changeset or ChildItem).
Example :
Tfs Path - $/APD-RepairSolutions/Main/Database
Mapped path - D:\TFS\APD-RepairSolutions/Main/Database.
I want a code, that would iteratively get the latest version of the entire folder Database,( that has number of tables,stored procedures etc.)
I am using ..
PS D:\Tfs\APD-RepairSolutions\Main\Database> $server=Get-TfsServer -Name http://tfs:8080/tfs
PS D:\Tfs\APD-RepairSolutions\Main\Database> Get-TfsChangeset -Recurse -Server $Server
Not helping my case - as it is only returning the latest changeset in the current directory.
To get latest (tf get) use Update-TfsWorkspace.
Get-TfsChangeset is the equivalent of tf changeset.
Gotcha! with Update-TFSWorskpace. Has some helpful parameters as well. -Items is used to specify the exact items you want to update.
PS D:\Tfs\APD-RepairSolutions\Main>Update-TFSWorkspace -All -Overwrite -Force -Recurse -Items .\Database
The Workspace is replaced with updated versions of items. Thanks #Kmoraz!
If you would like to use the TFS API instead, you can use the Workspace.Get Method:
# Load the TFS assemblies
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")
$ws = $vcServer.QueryWorkspaces("<WORKSPACENAME>", $null, $null);
# Specify properties of intended workspace get
$recursionType = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$latestVersion = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest
$getOptions = [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::GetAll
# Get what I want!
$ws.Get("$/Remote/Folder", $latestVersion, $recursionType, $getOptions)
Would be a good idea to replace the nulls with your domain login and computer name when Querying Workspaces.