Get Tfs Shelveset file contents at the command prompt? - command-line

I'm interested in getting the contents of a shelveset at the command prompt. Now, you would think that a cmdlet such as Get-TfsShelveset, available in the TFS Power Tools, would do this. You might also think that "tf.exe shelvesets" would do this.
However, unless I've missed something, I'm appalled to report that neither of these is the case. Instead, each command requires you to give it a shelveset name, and then simply regurgitates a single line item for that shelveset, along with some metadata about the shelveset such as creationdate, displayname, etc. But as far as I can tell, no way to tell what's actually in the shelf.
This is especially heinous for Get-TfsShelveset, which has the ability to include an array of file descriptors along with the Shelveset object it returns. I even tried to get clever, thinking that I could harvest the file names from using -WhatIf with Restore-TfsShelveset, but sadly Restore-TfsShelveset doesn't implement -WhatIf.
Please, someone tell me I'm wrong about this!

tf status /shelveset:name
will list out the content of the named shelveset (you can also supplier an owner: see tf help status).
With the TFS PowerToy's PowerShell snapin:
Get-TfsPendingChange -Shelveset name
for the same information.

It is possible to construct a small command-line application that uses the TFS SDK, which returns the list of files contained in a given shelveset.
The sample below assumes knowledge of the Shelveset name & it's owner:
using System;
using System.IO;
using System.Collections.ObjectModel;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace ShelvesetDetails
{
class Program
{
static void Main(string[] args)
{
Uri tfsUri = (args.Length < 1) ? new Uri("TFS_URI") : new Uri(args[0]);
TfsConfigurationServer configurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
ReadOnlyCollection<CatalogNode> collectionNodes = configurationServer.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.ProjectCollection },
false, CatalogQueryOptions.None);
CatalogNode collectionNode = collectionNodes[0];
Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection = configurationServer.GetTeamProjectCollection(collectionId);
var vcServer = teamProjectCollection.GetService<VersionControlServer>();
Shelveset[] shelves = vcServer.QueryShelvesets(
"SHELVESET_NAME", "SHELVESET_OWNER");
Shelveset shelveset = shelves[0];
PendingSet[] sets = vcServer.QueryShelvedChanges(shelveset);
foreach (PendingSet set in sets)
{
PendingChange[] changes = set.PendingChanges;
foreach (PendingChange change in changes)
{
Console.WriteLine(change.FileName);
}
}
}
}
}
Invoking this console app & catching the outcome during execution of the powershell should be possible.

Try:
tfpt review
/shelveset:shelvesetName;userName
You may also need to add on the server option so something like:
tfpt review /shelveset:Code Review;jim
/sever:company-source
I think this is what you are looking for.

This is what I ended up with, based on pentelif's code and the technique in the article at http://akutz.wordpress.com/2010/11/03/get-msi/ linked in my comment.
function Get-TfsShelvesetItems
{
[CmdletBinding()]
param
(
[string] $ShelvesetName = $(throw "-ShelvesetName must be specified."),
[string] $ShelvesetOwner = "$env:USERDOMAIN\$env:USERNAME",
[string] $ServerUri = $(throw "-ServerUri must be specified."),
[string] $Collection = $(throw "-Collection must be specified.")
)
$getShelvesetItemsClassDefinition = #'
public IEnumerable<PendingChange> GetShelvesetItems(string shelvesetName, string shelvesetOwner, string tfsUriString, string tfsCollectionName)
{
Uri tfsUri = new Uri(tfsUriString);
TfsConfigurationServer configurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
ReadOnlyCollection<CatalogNode> collectionNodes = configurationServer.CatalogNode.QueryChildren( new[] { CatalogResourceTypes.ProjectCollection }, false, CatalogQueryOptions.None);
CatalogNode collectionNode = collectionNodes.Where(node => node.Resource.DisplayName == tfsCollectionName).SingleOrDefault();
Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection = configurationServer.GetTeamProjectCollection(collectionId);
var vcServer = teamProjectCollection.GetService<VersionControlServer>();
var changes = new List<PendingChange>();
foreach (Shelveset shelveset in vcServer.QueryShelvesets(shelvesetName, shelvesetOwner))
{
foreach (PendingSet set in vcServer.QueryShelvedChanges(shelveset))
{
foreach ( PendingChange change in set.PendingChanges )
{
changes.Add(change);
}
}
}
return changes.Count == 0 ? null : changes;
}
'#;
$getShelvesetItemsType = Add-Type `
-MemberDefinition $getShelvesetItemsClassDefinition `
-Name "ShelvesetItemsAPI" `
-Namespace "PowerShellTfs" `
-Language CSharpVersion3 `
-UsingNamespace System.IO, `
System.Linq, `
System.Collections.ObjectModel, `
System.Collections.Generic, `
Microsoft.TeamFoundation.Client, `
Microsoft.TeamFoundation.Framework.Client, `
Microsoft.TeamFoundation.Framework.Common, `
Microsoft.TeamFoundation.VersionControl.Client `
-ReferencedAssemblies "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Client.dll", `
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Common.dll", `
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.VersionControl.Client.dll" `
-PassThru;
# Initialize an instance of the class.
$getShelvesetItems = New-Object -TypeName "PowerShellTfs.ShelvesetItemsAPI";
# Emit the pending changes to the pipeline.
$getShelvesetItems.GetShelvesetItems($ShelvesetName, $ShelvesetOwner, $ServerUri, $Collection);
}

Spent a few days trying to do this as well, this always popped up on google so here is what I found to help future generations:
To get the contents of the shelveset (at least with Team Explorer Everywhere),
use the command: tf difference /shelveset:<Shelveset name>
That will print out the contents of the shelveset and give filenames in the form :
<Changetype>: <server file path>; C<base change number>
Shelved Change: <server file path again>;<shelveset name>
So if your file is contents/test.txt
in the shelveset shelve1 (with base revision 1), you will see :
edit: $/contents/file.txt;C1
Shelved Change: $/contents/file.txt;shelve1
After that, using the tf print command
(or view if not using TEE) on $/contents/file.txt;shelve1 should get you the contents :
tf print $/contents/file.txt;shelve1
Shows you what is in the file.txt in shelveset shelve1

If you want get shelveset changes from server by using tfs command
Using power shell:
Get-TfsPendingChange -Server http://example.com/org -Shelveset shelvsetName
Using vs commands:
c:\projects>tf shelvesets BuddyTest_23
more info about this please see here
https://learn.microsoft.com/en-us/azure/devops/repos/tfvc/shelvesets-command?view=azure-devops

Related

Auto Login SAP GUI with powershell script

I'm trying to write a script that automatically logs into an SAP system via SAP GUI. I want the SAP GUI fields to be filled automatically with the script below.
Can you tell me, if I'm on the right way? How can I let it work?
#-Begin-----------------------------------------------------------------
#-Includes------------------------------------------------------------
."$PSScriptRoot\COM.ps1"
$hWSH = Create-Object "Wscript.Shell"
$hWSH.Popup("testmessage", 2, "goto", 1)
Free-Object $hWSH
#-Signatures----------------------------------------------------------
$Sig = #'
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
'#
# FindWindow function---------------------------------------------
$Win32 = Add-Type -Namespace Win32 -Name Funcs -MemberDefinition $Sig -PassThru
#-Set the path to the SAP GUI directory-------------------------------
$SAPGUIPath = "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\"
# SAP system ID-----------------------------------------------
$SID = "test.lan"
#instance number of the SAP system---------------------------
$InstanceNo = "10"
#-Start SAP GUI---------------------------------------------------
$SAPGUI = $SAPGUIPath + "sapgui.exe"
& $SAPGUI $SID $InstanceNo
#-Wait until the session is available---------------------------------
While ($Win32::FindWindow("SAP_FRONTEND_SESSION", "SAP") -eq 0) {
Start-Sleep -Milliseconds 250
}
#-Logon to SAP --------------------------------------------
$user="test"
$SAPGUI.document.getElementById("Benutzer").value= "$user"
$SAPGUI.document.getElementById("loginform").submit()
```
You could use SAP shortcut:
cd "c:\Program Files (x86)\SAP\FrontEnd\SAPgui\"
sapshcut -guiparm="[hostname] [installation number]" -system=[system id] -client=[client] -user=[user name] -pw=[password]
Replace the parameters (square brackets) with the appropriate values. You will see a confirmation popup when you execute this command for the first time (for a specific set of parameters), but you can disable the dialog for future automatic logins.
You may omit parameter -guiparm="[hostname] [installation number]" if the system ID is created in SAPlogon.

PowerShell Active Directory list all Multi-Valued attributes

not sure if I'm chaing the wild goose here but as per subject I'd need to get a list of AD attributes, for the user ObjectClass, that are multie valued.
For example the proxyAddresses exchange specific attribute is multi valued where extensionAttribute* only accept a single string.
We use a heavily customized schema and while I could go through each attribute documentation I'd rather get a list of aforementioned attributes via PowerShell.
I've tried using ldp.exe but could not achive desired results and was wondering if there is a way to do this via PowerShell or .Net managed code.
Thanks in advance for any help/pointer.
So you have to query the Schema part of the directory and look for objectClass attributeSchema and attribute isSingleValued (FALSE). The part of the distinguichName wichh is invariant is : CN=Schema,CN=Configuration.
try first with CSV :
csvde -f MultivaluedAttributes.csv -d CN=Schema,CN=Configuration,DC=MySubdomain,DC=MyDomain,DC=com -r "(&(objectclass=attributeSchema)(isSingleValued=FALSE))" -l lDAPDisplayName
Here is the powershell code.
# LDAPSchemaQuery.PS1
try
{
$dn = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://179.22.21.01/CN=Schema,CN=Configuration,DC=MyDomain,DC=MyExt","MyUser", 'MyPassword')
# Query into the Schema
# csvde -f MultivaluedAttributes.csv -d CN=Schema,CN=Configuration,DC=office,DC=coyotesystems,DC=com -r "(&(objectclass=attributeSchema)(isSingleValued=FALSE))" -l lDAPDisplayName
$Rech = new-object System.DirectoryServices.DirectorySearcher($dn)
#$Rech.filter = "(&(objectclass=user)(mail=*)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))"
$Rech.filter = "(&(objectclass=attributeSchema)(isSingleValued=FALSE))"
$Rech.SearchScope = "subtree"
$dumy=$Rech.PropertiesToLoad.Add("lDAPDisplayName")
$adAttributes = $Rech.findall()
foreach ($adAttribute in $adAttributes)
{
$multivaluedAttribute = try{$adAttribute.Properties["lDAPDisplayName"]}catch{""}
$multivaluedAttribute
}
}
catch
{
Write-Verbose "LDAPSchemaQuery : PB with $($adAttribute.Path)"
}

Calling AppDomain.DoCallback from Powershell

This is based on the Stack Overflow question: How to load an assembly as reflection-only in a new AppDomain?
I am attempting to determine the runtime version of an assembly, but that assembly could be loaded multiple times as I traverse through nested folders. Loading the assembly directly using
[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)
will therefore not work, as the assembly can only be loaded once in the app-domain.
Given the following function to load an assembly in a separate AppDomain:
function Load-AssemblyInNewAppDomain($assembly)
{
Write-Host $assembly.FullName
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid())
$domain.DoCallback
({
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
})
}
This outputs the contents of the delegate to the console, rather than executing it:
OverloadDefinitions
-------------------
void DoCallBack(System.CrossAppDomainDelegate callBackDelegate)
void _AppDomain.DoCallBack(System.CrossAppDomainDelegate theDelegate)
$loaded = [Reflection.Assembly]::Load($assembly)
$runtime = $loaded.ImageRuntimeVersion
Write-Host $runtime
Note that the results are the same, whether I use PowerShell 4 or 5
Any help/guidance appreciated
First thought: don't muck around with AppDomains at all and use a completely separate process. Those are (relatively) easily launched from PowerShell, at least. The drawback is that it's potentially much slower if you're doing this for lots of files.
$myAssemblyPath = "C:\..."
$getImageRuntimeVersion = {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($input).ImageRuntimeVersion
}
$encodedCommand = [Convert]::ToBase64String(
[Text.Encoding]::Unicode.GetBytes($getImageRuntimeVersion)
)
$imageRuntimeVersion = $myAssemblyPath | powershell -EncodedCommand $encodedCommand
So, is there no way at all to do this with AppDomains in PowerShell? Well, there is, but it's not pretty. You can't use AppDomain.DoCallBack because, as you've discovered, PowerShell can't remote delegates that way (because, under the covers, it produces dynamic methods).
However, it's easy to host the PowerShell runtime, and all PowerShell objects know how to serialize (a requirement for cross-domain remoting), so invoking a PowerShell script in another AppDomain is fairly simple (but still ugly):
$scriptInvokerAssembly = [System.IO.Path]::GetTempFileName() + ".dll"
Add-Type -OutputAssembly $tempAssembly -TypeDefinition #"
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Management.Automation;
public class ScriptInvoker : MarshalByRefObject {
public IEnumerable<PSObject> Invoke(ScriptBlock scriptBlock, PSObject[] parameters) {
using (var powerShell = PowerShell.Create()) {
powerShell.Commands.AddScript(scriptBlock.ToString());
if (parameters != null) {
powerShell.AddParameters(parameters);
}
return powerShell.Invoke();
}
}
}
"#
[Reflection.Assembly]::LoadFile($scriptInvokerAssembly) | Out-Null
Function Invoke-CommandInTemporaryAppDomain([ScriptBlock] $s, [object[]] $arguments) {
$setup = New-Object System.AppDomainSetup
$setup.ApplicationBase = Split-Path ([ScriptInvoker].Assembly.Location) -Parent
$domain = [AppDomain]::CreateDomain([Guid]::NewGuid(), $null, $setup)
$scriptInvoker = $domain.CreateInstanceAndUnwrap(
[ScriptInvoker].Assembly.FullName, [ScriptInvoker]
);
$scriptInvoker.Invoke($s, $arguments)
[AppDomain]::Unload($domain)
}
And now you can do
Invoke-CommandInTemporaryAppDomain {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($args[0]).ImageRuntimeVersion
} $myAssemblyPath
Note that we have to generate a temporary assembly on disk and have AppDomain load it from there. This is ugly, but you can't have Add-Type produce an in-memory assembly, and even if you do end up with a byte[] getting that to load in another AppDomain is anything but trivial because you can't hook AppDomain.AssemblyResolve in PowerShell. If this command was packaged in a module, you'd compile the assembly containing the ScriptInvoker ahead of time, so I don't see working around this as a priority.
You can't run DoCallback via powershell alone. But DoCallBack does work with some inline C#. As Jeroen says it's ugly, but this works:
$assm = "C:\temp\so\bin\dynamic-assembly.dll"
Add-Type -TypeDefinition #"
using System.Reflection;
using System;
namespace Example
{
public class AppDomainUtil
{
public void LoadInAppDomain(AppDomain childDomain, string assemblyName)
{
childDomain.SetData("assemblyName", assemblyName);
childDomain.DoCallBack( new CrossAppDomainDelegate(LoadAssembly)) ;
}
public static void LoadAssembly()
{
string assemblyName = (string)AppDomain.CurrentDomain.GetData("assemblyName");
// console not available from another domain
string log = "c:\\temp\\hello.txt";
System.IO.File.WriteAllText(log, string.Format("Hello from {0}\r\n",AppDomain.CurrentDomain.FriendlyName));
System.IO.File.AppendAllText(log, string.Format("Assembly to load is {0}\r\n",assemblyName));
Assembly loaded = Assembly.Load(assemblyName);
System.IO.File.AppendAllText(log, string.Format("Assemblyloaded: {0}\r\n",loaded.FullName));
}
}
}
"# -OutputAssembly $assm -OutputType Library # must set output assembly otherwise assembly generated in-memory and it will break with Type errors.
Add-Type -Path $assm
function Load-AssemblyInNewAppDomain([string]$assembly) {
Write-Host "Parent domain: $([AppDomain]::CurrentDomain.FriendlyName)"
$util = New-Object Example.AppDomainUtil
$ads = New-Object System.AppDomainSetup
$cd = [AppDomain]::CurrentDomain
# set application base
$ads.ApplicationBase = [IO.path]::GetDirectoryName( $assm )
[System.AppDomain]$newDomain = [System.AppDomain]::CreateDomain([System.Guid]::NewGuid().ToString(), $null, $ads);
Write-Host "Created child domain: $($newDomain.FriendlyName)"
$util.LoadInAppDomain($newDomain, $assembly)
}
Testing it out:
PS C:\WINDOWS\system32> Load-AssemblyInNewAppDomain "".GetType().Assembly.FullName
Parent domain: PowerShell_ISE.exe
Created child domain: 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
PS C:\WINDOWS\system32> cat C:\temp\hello.txt
Hello from 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
Assembly to load is mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Assemblyloaded: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Update TFS Build Status via Powershell

I've a TFS Build Definition setup where, I execute a powershell script for security hardening, applying final patches etc.
Sometimes this powershell script can fail and I would like to change the status of build to be un-successfull or fail. Is there a way to change this TFS Build status via a powershell?
Try this:
$script:TFServerName = $ServerName
$script:TFServer = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($ServerName)
$script:TFBuildServer = $TFServer.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
$spec = $TFBuildServer.CreateBuildDetailSpec($TeamProject, $DefinitionName)
$spec.Status = 'Failed'
Make sure you've Add-Type the right versions of the TFS assemblies. I'm assuming here that since the properties support setters, they communicate back to the server to affect the change. But I'm not 100% sure of that.
Here's what I pieced together from MSDN:
[Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Build.Client")
[Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")
$tfsServerAddress = "http://{tfs hostname}:{port}/{CollectionName}"
$build = "{build definition name}_{build date}.{build count for the day}"
#update build status
$tfsProject = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServerAddress)
$tfsBuildServer = $tfsProject.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
$spec = $tfsBuildServer.CreateBuildDetailSpec($teamProject)
$spec.BuildNumber = $build
$builds = $tfsBuildServer.QueryBuilds($spec).Builds
if ($builds[0].TestStatus -eq 'Succeeded'){
echo "updating test status:" $builds[0].TestStatus "to $testOutcome"
$builds[0].TestStatus = $testOutcome
}
if ($builds[0].Status -eq 'Succeeded'){
echo "updating build status:" $builds[0].Status "to $testOutcome"
$builds[0].Status = $testOutcome
}
$tfsBuildServer.SaveBuilds($builds)
Keep in mind that you need a special permission to update build status

How to get list of TFS builds running at the moment from command line?

I'm trying to automate the deployment process, and as part of it, I need to run my release build from command line. I can do it, using command like
.\TFSBuild start http://server-name:8080/tfs/project-collection project-name build-name priority:High /queue
It even returns some code for the queued build — Build queued. Queue position: 2, Queue ID: 11057.
What I don't know, is how to get info about currently running builds, or about the state of my running build from powershell command line? The final aim is to start publishing after that build completes.
I've already got all necessary powershell scripts to create the deployment package from the build results, zip it, copy to production and install there. All I need now — to know when my build succeedes.
This function will wait for a build with the Queue ID given by TFSBuild.exe:
function Wait-QueuedBuild {
param(
$QueueID
)
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')
$uri = [URI]"http://server-name:8080/tfs/project-collection"
$projectCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($uri)
$buildServer = $projectCollection.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
$spec = $buildServer.CreateBuildQueueSpec('*','*')
do {
$build = $buildServer.QueryQueuedBuilds($spec).QueuedBuilds| where {$_.Id -eq $QueueID}
sleep 1
} while ($build)
}
You can get the id returned by TFSBuild.exe, then call the function.
$tfsBuild = .\TFSBuild start http://server-name:8080/tfs/project-collection project-name build-name priority:High /queue
Wait-QueuedBuild [regex]::Match($tfsBuild[-1],'Queue ID: (?<id>\d+)').Groups['id'].Value
Using the work by E.Hofman available here it is possible to write a C# console app that uses TFS SDK and reveals if any build agent is currently running as follows:
using System;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
namespace ListAgentStatus
{
class Program
{
static void Main()
{
TfsTeamProjectCollection teamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://TFSServer:8080"));
var buildServer = teamProjectCollection.GetService<IBuildServer>();
foreach (IBuildController controller in buildServer.QueryBuildControllers(true))
{
foreach (IBuildAgent agent in controller.Agents)
{
Console.WriteLine(agent.Name+" is "+agent.IsReserved);
}
}
}
}
}
The parameter .IsReserved is what toggles to 'True' during execution of a build.
I 'm sorry my powershell skills are not good enough for providing with a PS variant of the above. Please take a look here, where the work by bwerks might help you do that.
# load classes for execution
[Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Build.Client") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client") | Out-Null
# declare working variables
$Uri = New-Object System.Uri "http://example:8080/tfs"
# get reference to projection collection
$ProjectCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($Uri)
# get reference to build server
$BuildServer = $ProjectCollection.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
# loop through the build servers
foreach($Controller in $BuildServer.QueryBuildControllers($true))
{
# loop through agents
foreach($BuildAgent in $Controller.Agents)
{
Write-Host "$($BuildAgent.Name) is $($BuildAgent.IsReserved)"
}
}