"GetLatest" with Powershell doesn't download files on new TFS workspace - powershell

I'm trying to create a Powershell script that will setup a brand new workspace in a temporary location, do a GetLatest on selected solutions/projects, and download the source code so that I can then trigger further build/versioning operations.
I think I have the script more or less right, but the problem is every time I run this, it tells me there were 0 operations... i.e. I already have the latest versions. This results in nothing at all being downloaded.
Can anyone see what I'm doing wrong?
$subfolder = [System.Guid]::NewGuid().ToString()
$tfsServer = "http://tfsserver:8080/tfs"
$projectsAndWorkspaces = #(
#("$/Client1/Project1","D:\Builds\$subfolder\Client1\Project1"),
#("$/Client1/Project2","D:\Builds\$subfolder\Client1\Project2"),
)
$tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServer)
$tfsVersionCtrl = $tfsCollection.GetService([type] "Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer")
$tfsWorkspace = $tfsVersionCtrl.CreateWorkspace($subfolder, $tfsVersionCtrl.AuthorizedUser)
Write-Host "Operations:"
foreach ($projectAndWs in $projectsAndWorkspaces)
{
if (-not(Test-Path $projectAndWs[1]))
{
New-Item -ItemType Directory -Force -Path $projectAndWs[1] | Out-Null
}
$tfsWorkspace.Map($projectAndWs[0], $projectAndWs[1])
$recursion = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$itemSpecFullTeamProj = New-Object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectAndWs[0], $recursion)
$fileRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($itemSpecFullTeamProj, [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest)
$getStatus = $tfsWorkspace.Get($fileRequest, [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite)
Write-Host ("[{0}] {1}" -f $getStatus.NumOperations, ($projectAndWs[0].Substring($projectAndWs[0].LastIndexOf("/") + 1)))
}
Write-Host "Finished"

The $tfsServer = "http://tfsserver:8080/tfs" should be $tfsServer = "http://tfsserver:8080/tfs/nameOfACollection"

The "$/Client1/Project1" string smells. I would add a backtick before the dollar sign so it is not read as a variable or use single quotes.
Backtick
"`$/Client1/Project1"
Single quote
'$/Client1/Project1'

Related

Edit and Save DOCX Using Powershell

I have slightly modified this PowerShell script for my work with DOCX files but would like to edit the DOCX file in place.
After running the script, the terminal displays the error message: "You cannot call a method on a null-value expression" in reference to line 43, $document.Save().
I am not sure the reason for the error as the path to $document is already defined earlier in the script. What am I missing?
Here is the whole script:
Param ([string]$path = $(throw "-path is required."))
Import-Module "C:\scripts\PSGenericMethods.psm1"
[System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Open XML SDK\V2.5\lib\DocumentFormat.OpenXml.dll") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Packaging") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Wordprocessing") | out-null
[Reflection.Assembly]::LoadWithPartialName("OpenXmlPowerTools") | out-null
[DocumentFormat.OpenXml.Packaging.WordprocessingDocument]$document = $null
$document = [DocumentFormat.OpenXml.Packaging.WordprocessingDocument]::Open($path, $true)
[DocumentFormat.OpenXml.Packaging.MainDocumentPart]$MainDocumentPart = $document.MainDocumentPart
[DocumentFormat.OpenXml.Wordprocessing.Document]$InnerDocument = $document.Document
[DocumentFormat.OpenXml.Wordprocessing.Body]$Body = $document.Body
[DocumentFormat.OpenXml.Wordprocessing.Paragraph]$paragraph = $document.Paragraph
[DocumentFormat.OpenXml.Wordprocessing.ParagraphMarkRunProperties]$ParagraphMarkRunProperties = $document.ParagraphMarkRunProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties]$ParagraphProperties = $document.ParagraphProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId]$ParagraphStyleId = $document.ParagraphStyleId
[DocumentFormat.OpenXml.Wordprocessing.Run]$run = $document.Run
$paragraphs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Paragraph
$runs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Run
foreach ($run in $runs) {
if ($run.RunProperties.Languages.Val) {
<#[String]$value = $run.InnerText#>
[String]$language = $run.RunProperties.Languages.Val
'{{$span xml:lang="{0}"$}}{1}{{$/span}}$' -f $language, $run.InnerText
}
}
$document.close()
Update:
After modifying the script, I now no longer have the problem with "You cannot call a method on a null value expression" error. The problem is that I am not sure how to save results to the file.
This may be a duplicate of this thread.
Can anyone suggest a method for saving the changes to the file?
Variable names in PowerShell are NOT case-sensitive. You destroy your "document" with this line I think:
[DocumentFormat.OpenXml.Wordprocessing.Document]$Document = $document.Document
Because $Document is the same variable as $document.

Output ALL results at the end of foreach instead of during each run

I inherited a script which loops through a set of servers in a server list and then outputs some stuff for each one. It uses StringBuilder to append stuff to a variable and then spits out the results...how do I get the script to store the contents so I can display it at the VERY end with the results of the entire foreach instead of having it print (and then overwrite) on each iteration?
Currently my results look like this:
ServerName1
Text1
Next run:
ServerName2
Text 2
How do I get it to store the data and then output the following at the end so I can email it?
ServerName1
Text1
ServerName2
Text2
My code:
foreach($Machine in $Machines)
{
Invoke-Command -ComputerName $Machine -ScriptBlock{param($XML1,$XML2,$XML3,$URL)
[System.Text.StringBuilder]$SB = New-Object System.Text.StringBuilder
$X = $SB.AppendLine($env:COMPUTERNAME)
if (Test-Path <path>)
{
$PolResponse = <somestuff>
$PolResponse2 = <somestuff>
Write-Host "[1st] $PolResponse" -ForegroundColor Magenta
Write-Host "[2nd] $PolResponse2" -ForegroundColor Magenta
$X = $SB.AppendLine($PolResponse)
$X = $SB.AppendLine($PolResponse2)
}
else
{
$PolResponse = "[1st] No Response"
$PolResponse2 = "[2nd] No Response"
Write-Host $PolResponse -ForegroundColor Red
Write-Host $PolResponse2 -ForegroundColor Red
$X = $SB.AppendLine($PolResponse)
$X = $SB.AppendLine($PolResponse2)
}
} -ArgumentList $XML1, $XML2, $XML3, $URL
}
# Sending result email
<<I want to send the TOTALITY of $SB here>>
You can start by moving the StringBuilder variable declaration outside of the for loop (prior to it)
[System.Text.StringBuilder]$SB = New-Object System.Text.StringBuilder
then FOR LOOP
I don't know if this will be a good solution for what you're asking for or not, but what you could do is create a txt file and every loop in the foreach loop add the information to a txt file. This is one way to store all of the information and then have all of it together at the end.
New-Item -Path "\\Path\to\file.txt" -Itemtype File
Foreach(){
$Stuff = # Do your stuff here
Add-Content -Value $stuff -Path "\\Path\to\file.txt"
}
# Email .txt file ?
# You could use Send-MailMessage to do this possibly
Hopefully this can be helpful for your goal.

Locate MSTest.exe using powershell

I'm in the process of automating my .Net solution build to be completely in PowerShell. I want to locate MSTest.exe using PowerShell.
I used the following script to locate MSBuild.exe and I hope that I can have something similar to locate MSTest.exe
$msBuildQueryResult = reg.exe query "HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0" /v MSBuildToolsPath
$msBuildQueryResult = $msBuildQueryResult[2]
$msBuildQueryResult = $msBuildQueryResult.Split(" ")
$msBuildLocation = $msBuildQueryResult[12] + "MSBuild.exe"
Any directions ?
The following works with Visual Studio 2010 and higher[1]:
# Get the tools folder location:
# Option A: Target the *highest version installed*:
$vsToolsDir = (
Get-Item env:VS*COMNTOOLS | Sort-Object {[int]($_.Name -replace '[^\d]')}
)[-1].Value
# Option B: Target a *specific version*; e.g., Visual Studio 2010,
# internally known as version 10.0.
# (See https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#History)
$vsToolsDir = $env:VS100COMNTOOLS
# Now locate msbuild.exe in the "IDE" sibling folder.
$msTestExe = Convert-Path -EA Stop (Join-Path $vsToolsDir '..\IDE\MSTest.exe')
The approach is based on this answer and is generalized and adapted to PowerShell.
It is based on system environment variables VS*COMNTOOLS, created by Visual Studio setup, where * represents the VS version number (e.g., 100 for VS 2010).
Re option A: Sort-Object is used to ensure that the most recent Visual Studio installation is targeted, should multiple ones be installed side by side:
The script block used for sorting first extracts only the embedded version number from the variable name ($_.Name -replace '[^\d]'; e.g., 100 from VS100COMNTOOLS) and converts the result to an integer ([int]); [-1] then extracts the last element from the sorted array - i.e., the variable object whose names has the highest embedded version number - and accesses its value (.Value).
The IDE subfolder, in which MSTest.exe is located is a sibling folder of the tools folder that VS*COMNTOOLS points to.
If MSTest.exe is NOT in the expected location, Convert-Path will throw a non-terminating error by default; adding -EA Stop (short for: -ErrorAction Stop) ensures that the script is aborted instead.
[1]
- I've tried up to Visual Studio 2015; do let me know whether or not it works on higher versions.
- Potentially also works with VS 2008.
Perhaps you are wanting something like this?
$regPath = "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0"
$regValueName = "MSBuildToolsPath"
$msBuildFilename = "MSBUild.exe"
if ( Test-Path $regPath ) {
$toolsPath = (Get-ItemProperty $regPath).$regValueName
if ( $toolsPath ) {
$msBuild = Join-Path $toolsPath $msBuildFilename
if ( -not (Test-Path $msBuild -PathType Leaf) ) {
Write-Error "File not found - '$msBuild'"
}
}
}
# Full path and filename of MSBuild.exe in $msBuild variable
My way of getting mstest path.
GetMSTestPath function is main function which you call and then if first GetMsTestPathFromVswhere function will find something it returns path if not your will be making a long search for mstest.exe. Usually, it takes approximately 10 sec. I know that this is not the best but at least it is something when you struggle to find mstest.exe. Hope it will be helpful for somebody. :)))
function GetMSTestPath
{
function GetTime()
{
$time_now = Get-Date -format "HH:mm:ss"
return $time_now;
}
function GetMsTestPathFromVswhere {
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$path = & $vswhere -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath
#write-host $path
if ($path) {
$tool = join-path $path 'Common7\IDE\MSTest.exe'
if (test-path $tool) {
return $tool
}
return ""
}
}
function SeachForMsTestPath
{
write-host $(GetTime)
$path = Get-ChildItem C:\ -Filter MSTest.exe -Recurse -ErrorAction Ignore | ? { $_.VersionInfo.FileDescription -eq 'Test Execution Command Line Tool' } | Select -First 1
write-host $(GetTime)
return $path
}
$msTestExePath = GetMsTestPathFromVswhere
if ([string]::IsNullOrEmpty($msTestExePath))
{
$msTestExePath = SeachForMsTestPath;
if ([string]::IsNullOrEmpty($msTestExePath))
{
Write-host "MsTest path is not found. Exiting with error"
Exit -1
}
}
return $msTestExePath;
}
Thanks #Bill_Stewart , I used your comments to write this working function:
function Get-MSTest-Location {
$msTests = #()
$searchResults = Get-ChildItem C:\* -Filter MSTest.exe -Recurse -ErrorAction Ignore
foreach($searchResult in $searchResults) {
try{
if(($searchResult.VersionInfo -ne $null) -and ($searchResult.VersionInfo.FileDescription -eq "Test Execution Command Line Tool"))
{ $msTests = $msTests + $searchResult.FullName }
}
catch{}
}
if($msTests.Length -eq 0)
{return "MSTest not found."}
return $msTests[0]
}

How to count files in FTP directory

I have this script. I'm trying to count how many file are in.
clear
$ftp_uri = "ftp://ftp.domain.net:"
$user = "username"
$pass = "password"
$subfolder = "/test/out/"
$ftp_urix = $ftp_uri + $subfolder
$uri=[system.URI] $ftp_urix
$ftp=[system.net.ftpwebrequest]::Create($uri)
$ftp.Credentials=New-Object System.Net.NetworkCredential($user,$pass)
#Get a list of files in the current directory.
$ftp.Method=[system.net.WebRequestMethods+ftp]::ListDirectorydetails
$ftp.UseBinary = $true
$ftp.KeepAlive = $false
$ftp.EnableSsl = $true
$ftp.Timeout = 30000
$ftp.UsePassive=$true
try
{
$ftpresponse=$ftp.GetResponse()
$strm=$ftpresponse.GetResponseStream()
$ftpreader=New-Object System.IO.StreamReader($strm,'UTF-8')
$list=$ftpreader.ReadToEnd()
$lines=$list.Split("`n")
$lines
$lines.Count
$ftpReader.Close()
$ftpresponse.Close()
}
catch{
$_|fl * -Force
$ftpReader.Close()
$ftpresponse.Close()
}
In the directory I have three files but $lines.count return 4. $lines have 4 rows, three files and an empty line. Somebody can explain me the mystery?
The $list contains:
file1`nfile2`nfile3`n
If you split the string by "`n", you (correctly) get four parts, with the last one being empty.
You can use an overload of String.Split that takes StringSplitOptions and use RemoveEmptyEntries:
$list.Split("`n", [System.StringSplitOptions]::RemoveEmptyEntries)

How to pin to start menu using PowerShell

I can pin some programs to taskbar on Win7 using PowerShell.
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}
How do I modify the above code to pin a program to the Start menu?
Another way
$sa = new-object -c shell.application
$pn = $sa.namespace($env:windir).parsename('notepad.exe')
$pn.invokeverb('startpin')
Or unpin
$pn.invokeverb('startunpin')
Use the code below
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Start Men&u'}
if ($verb) {$verb.DoIt()}
Note: the change is in the fourth line.
The main problem with most of the solution is that they enumerate the verbs on a file, search for the string to perform the action (“Pin to Startmenu” etc.) and then execute it. This does not work if you need to support 30+ languages in your company, except you use external function to search for the localized command (see answer from shtako-verflow).
The answer from Steven Penny is the first that is language neutral and does not need any external code. It uses the verbs stored in the registry HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449} and HKEY_CLASSES_ROOT\CLSID\{a2a9545d-a0c2-42b4-9708-a0b2badd77c8}
Based on this, here’s the code we are now using:
function PinToTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarpin"
}
function UnpinFromTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarunpin"
}
function PinToStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startpin"
}
function UnpinFromStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startunpin"
}
function ExecuteVerb {
param(
[Parameter(Mandatory=$true)][string]$File,
[Parameter(Mandatory=$true)][string]$Verb
)
$path = [System.Environment]::ExpandEnvironmentVariables($File)
$basePath = split-path $path -parent #retrieve only the path File=C:\Windows\notepad.exe -> C:\Windows
$targetFile = split-path $path -leaf #retrieve only the file File=C:\Windows\notepad.exe -> notepad.exe
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace($basePath)
if ($folder)
{
$item = $folder.Parsename($targetFile)
if ($item)
{
$item.invokeverb($Verb)
# "This method does not return a value." (http://msdn.microsoft.com/en-us/library/windows/desktop/bb787816%28v=vs.85%29.aspx)
# Therefore we have no chance to know if this was successful...
write-host "Method [$Verb] executed for [$path]"
}
else
{
write-host "Target file [$targetFile] not found, aborting"
}
}
else
{
write-host "Folder [$basePath] not found, aborting"
}
}
#PinToTaskbar "%WINDIR%\notepad.exe"
#UnpinFromTaskbar "%WINDIR%\notepad.exe"
PinToStartmenu "%WINDIR%\notepad.exe"
#UnpinFromStartmenu "%WINDIR%\notepad.exe"
See the script (international) here : http://gallery.technet.microsoft.com/scriptcenter/b66434f1-4b3f-4a94-8dc3-e406eb30b750
If you want to add an action like Pin to Modern UI interface (Windows 8), at $verbs, add 51201
Steven Penny's second answer above worked well for me. Here are a couple more tidbits.
It's doing COM through PowerShell, so you can do the same thing with pretty much any COM client. For example, here's an AutoHotkey version.
Shell := ComObjCreate("Shell.Application")
Target := Shell.Namespace(EnvGet("WinDir")).ParseName("Notepad.exe")
Target.InvokeVerb("startpin")
VBScript or InnoSetup would look almost the same except for the function used to create the object.
I also found that I have one program that pinned OK, but didn't have the right icon and/or description because of limitations in the compiler. I just made a little 1-line WinForms app that starts the target with Process.Start, and then added the appropriate icon, and the name I wanted in the Start Menu in the Title property in AppInfo.cs.