How can I bulk rename files using PowerShell? - tfs

I'm trying to recursively rename a bunch of TFS folders using tf rename, PowerShell and a regular expression but I'm having some issues with PowerShell as I haven't spent much time with it. This is what I've put together so far to replace a leading 5 with 2.3.2 but it isn't working:
dir | foreach { tf rename $_ { $_.Name -replace '^5', '2.3.2' } }
Actual result:
Unrecognized command option 'encodedCommand'.
Unrecognized command option 'encodedCommand'.
Unrecognized command option 'encodedCommand'.
Unrecognized command option 'encodedCommand'.
...etc.
Update:
I got a little closer by doing the following instead:
dir | foreach { $newname = $_.Name -replace "^5", "2.3.2"; tf rename $_ $newname }
My next goal is to make this recurse subdirectories but this seems a bit more challenging (changing it to dir -recurse makes it quit after the parent folders for some reason).

I would first filter by 5* so you only process names that start with 5. Also, in this case since tf.exe isn't a PowerShell cmdlet, you don't want to use a scriptblock to determine a new name. Just use a grouping expression like so:
dir -filter 5* | foreach { tf rename $_ ($_.Name -replace '^5', '2.3.2')}
BTW, when you are trying to debug parameter passing to a native EXE like this it is immensely helpful to use the echoargs.exe utilty from the PowerShell Community Extensions. This is what it told me about your original approach:
6# dir -filter 5* | foreach { echoargs rename $_ { $_.Name -replace '^5', '2.3.2' } }
Arg 0 is <rename>
Arg 1 is <5foo.txt>
Arg 2 is <-encodedCommand>
Arg 3 is <IAAkAF8ALgBOAGEAbQBlACAALQByAGUAcABsAGEAYwBlACAAJwBeADUAJwAsACAAJwAyAC4AMwAuADIAJwAgAA==>
Arg 4 is <-inputFormat>
Arg 5 is <xml>
Arg 6 is <-outputFormat>
Arg 7 is <text>

Notes:
TFS has native cmdlets -- no need for tf.exe in most cases.
The time complexity of workspace operations depends on the number of pending renames already in the workspace. In TFS 2005/2008 it's significantly worse than linear. Bottom line, you should really consider batching up renames into multiple checkins if you have a large # of items, otherwise every single "tf rename" (or New-TfsPendingChange -Rename if using the cmdlets) will start taking minutes.

Try this:
dir . | foreach { $newname = $_.Name -replace "^5", "2.3.2"; tf rename $_ $newname }

Running the above commands requires the tf.exe to have been aliased as 'tf'.. or it did on my machine at least.
Run this command:
Set-Alias tf "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\tf.exe"
Update the path to tf.exe as appropriate
Also consider adding the line to your profile for future use
notepad $PROFILE

Related

how do i pass the selected string into a variable?

how do i store the output of a command into a variable for use in a github-actions .yaml?
docker images --format='{{.ID}}' | select -first 1
gives me:
fc6e040841a1
i've seen stuff online about select-object..but i honestly have no idea, just trying to push an image to a registry...
the following cmd doesn't work in powershell:
for /f "delims=" %a in ("docker images --format='{{.ID}}' | select -first 1") do #set "%_img%=%a"
the following cmd doesn't work in powershell:
for /f "delims=" %a in ("docker images --format='{{.ID}}' | select -first 1") do #set "%_img%=%a"
That's because this is Command Prompt syntax. Specifically, everything outside of the () would only work under cmd.exe. The PowerShell equivalent for assigning a command result to a variable is:
$variableName = COMMAND
To apply it to your use case:
$imageId = docker images --format='{{.ID}}' | Select-Object -First 1
Note that select is an alias of Select-Object and either can be used interchangeably.
Edit: While not required for setting variables unlike in the Command Prompt, for syntax is still different in PowerShell when batch scripting. You can read up on PowerShell's for, foreach, and ForEach-Object constructs when you want to learn how they are used in PowerShell scripts, and watch for this gotcha when using the foreach "statement" as part of a pipeline.
While not part of the original scope of the question, since OP did ask and I answered in the comments, I will put the bash equivalent here for the sake of completeness and how I transposed this from the PowerShell method I used above:
imageId=$(docker images --format="{{.ID}}" | head -n 1)
This is similar to the PowerShell syntax with a few changes: remove the $ from the variable name on assignment, and Select-Object is replaced by head. You can't pad the = with whitespace, and you have to subshell the command with $().

Open .sln file from cmder?

Is it possible to launch devenv.exe and open a .sln file from cmder? After I clone a git repo from the command line, I'd like to quickly open the solution in Visual Studio without having to leave cmder.
Danny is correct, you simply type xxx.sln.
I'm a bit lazy and developed the script below to be able to type vs and it will find and open the first sln file it finds in the current directory.
Here are some example commands:
vs - Will open the first sln file it finds, if it doesn't find one it will just open Visual Studio without a solution. I default to opening with Visual Studio 2017. Just change the default case in the Get-VisualStudioCommand function if you'd rather default to a different version.
vs 15 - I have a bunch of versions of Visual Studio on my machine, so this will open similarly to vs, but will open with Visual Studio 2015.
vs 13 'helloWorld.sln' - this will open a specific sln file, i.e., helloWorld.sln. One of the projects I worked on recently had multiple solutions in the same directory.
vs -WhatIf -Verbose - this will show you what the script would do if it ran and will print out the Write-Verbose messages. Handy for debugging. I show and example of this in the image below.
For this solution, I'm assuming that you're using a PowerShell console. If so, you can define a .ps1 file that will run before the console opens via the -File option. It's like defining a different PowerShell profile with different commands per console window (very powerful).
See the image below where I show going to settings and modify the PowerShell:PowerShell task to execute PowerShell -NoExit -NoLogo -File C:\src\ps\Cmder\general_setup.ps1 -new_console:d:"C:\src"
Here is the image (note, although I use an alias for ls that outputs in PowerShell like a Linux ls command...this is a PowerShell console):
If you add the following script to the C:\src\ps\Cmder\general_setup.ps1 file (or wherever you want to put it)...you can then type in vs and it will execute. Notice in the Get-VisualStudioCommand function, I've put all the file paths...not the most elegant looking code, but if your file paths to devenv.exe are different than mine, just change them there.
Note, you could also get fancy and add a pass-through to the -ArgumentList or flag for the /SafeMode command to be able to open Visual Studio in safe mode when needed, I've just created these as a quick and dirty shortcut.
function Get-VisualStudioCommand
{
[CmdletBinding()]
param ( [AllowNull()][String] $vsVersion )
$vs10 = """${env:ProgramFiles(x86)}\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe"""
$vs13 = """${env:ProgramFiles(x86)}\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe"""
$vs15 = """${env:ProgramFiles(x86)}\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe"""
$vs17 = """${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe"""
switch ($vsVersion)
{
'10' {$vs10}
'13' {$vs13}
'15' {$vs15}
'17' {$vs17}
default {$vs17}
}
}
function Get-SolutionName
{
[CmdletBinding()]
param ( [AllowNull()][String] $Name )
if (!$Name)
{
$Name = Get-ChildItem -Filter *.sln
}
if ($Name)
{
$Name = ('"{0}"' -f $Name)
}
return $Name
}
function Start-VisualStudioProcess
{
[CmdletBinding(SupportsShouldProcess=$true)]
param([String]$Version, [String]$Sln)
$VsCommand = Get-VisualStudioCommand -vsVersion $Version
$Sln = Get-SolutionName -Name $Sln
Write-Verbose -Message ('Starting: command={0} solutionName={1}' -f $VsCommand, $Sln)
if ($PSCmdlet.ShouldProcess($VsCommand, 'Start-Process'))
{
if ($Sln)
{
Start-Process -FilePath $vsCommand -ArgumentList $sln
}
else
{
Start-Process -FilePath $vsCommand
}
}
}
Set-Alias -Name vs -Value Start-VisualStudioProcess
Let me know if you have any questions. I hope it helps.
Run this command in your prompt(cmd, cmder, etc.)
reg add "HKCU\Software\Microsoft\Command Processor" /v Autorun /d "doskey vs=for /f \"delims=\" %a IN ('dir /b *.sln') do start %a" /f
Write "vs" for open the first *.sln in folder of Solution and be happy!

TFS 2015 build vNext update file version with commit id

Do you know how inject commit id into file version, so every assembly would heve version like 2.0.6565.0 where 6565 is related to C6565commit ID in TFS ?
It looks some power shell script is needed.
If your question is similar to your another post TFS 2015. the $(var.SourceLocation) variable is not available at gated-check in, that want to get the changeset id that hasn't checked in during gated check-in, then it's impossible in a single build.
If you don't use gated check in, then you can use $Env:BUILD_SOURCEVERSION in a powershell script to set the AssemblyVersion. Here is already a script at the website below, you can refer to it:
https://github.com/wulfland/ScriptRepository/blob/master/TFSBuild/TFSBuild/AssemblyVersion/Set-AssemblyVersion/Set-AssemblyVersion.ps1
Finally I created my own PS script based on this post.
The idea update version in all files with assembly info
$CommitId = ([string]$env:BUILD_SOURCEVERSION) -replace "[^0-9]+", ""
$AllVersionFiles = Get-ChildItem $SourceDir AssemblyInfo.cs -recurse
$regexToFindVersion = "Version\(""([0-9]+)\.([0-9]+).+"""
foreach ($file in $AllVersionFiles)
{
Write-Host "Processing " $file.FullName
(Get-Content $file.FullName) |
%{$_ -replace $regexToFindVersion, ('Version("$1.$2.0.' + $CommitId + '"') } |
Set-Content $file.FullName -Force
}
Full script can be found here.
The script must be placed before building project:

Search in/for text in files

I am currently learning LPTHW Ex 46. In his video tutorial, Zed had done the following commands:
Find NAME within files using grep -r "NAME" *.
Find all files with extension ending in .pyc using find . -name "*pyc" -print.
Unfortunately, the above code does not work on Windows PowerShell. May I know what their Windows PowerShell equivalents are?
Based on my search, item 1 can be replaced by Select-String. However, it is not as good as we can only search specific files and not directories. For example, while this would work:
Select-String C:\Users\KMF\Exercises\Projects\gesso\gesso\acrylic.py -pattern "NAME"
this would not:
Select-String C:\Users\KMF\Exercises\Projects\gesso -Pattern "NAME"
and it gives the following error
Select-String : The file C:\Users\KMF\Exercises\Projects\gesso can not be read: Access to the path 'C:\Users\KMF\Exercises\Projects\gesso' is denied.
For item 2 I could not find a similar function.
grep and find are Unix/Linux shell commands. They won't work in PowerShell unless you install a Windows port of them.
As you already found out, Select-String is the PowerShell equivalent for grep. It doesn't recurse by itself, though, so you have to combine it with Get-ChildItem to emulate grep -r:
Get-ChildItem -Recurse | Select-String -Pattern 'NAME'
For emulating find you'd combine Get-ChildItem with a Where-Object filter:
Get-ChildItem -Recurse | Where-Object { $_.Extension -eq '.pyc' }
PowerShell cmdlets can be aliased to help administrators avoid extensive typing (since PowerShell statements tend to be rather verbose). There are several built-in aliases, e.g. ls or dir for Get-ChildItem, and ? or where for Where-Object. You can also define aliases of your own, e.g. New-Alias -Name grep -Value Select-String. Parameter names can be shortened as long as the truncated parameter name remains unique for the cmdlet. When cmdlets allow positional parameters they can even be omitted entirely.
With all of the above your two PowerShell statements can be reduced to the following:
ls -r | grep 'NAME'
ls -r | ? { $_.Extension -eq '.pyc' }
Note however, that aliases and abbreviations are mainly intended as an enhancement for console use. For PowerShell scripts you should always use the full form, not only for readability, but also because aliases may differ from environment to environment. You don't want your scripts to break just because they're run by someone else.

How can I use PowerShell to update build qualities on previous TFS Builds?

We are using TFSDeployer to listen to build quality changes and deploy to our staging environment when it transitions to "Staging".
I'd like to have it go ahead and update all of the other builds that have a currently build quality of "Staging" to be "Rejected".
This appears to be something that needs to happen inside the PowerShell script which looks like:
$droplocation = $TfsDeployerBuildData.DropLocation
ECHO $droplocation
$websourcepath = $droplocation + "\Release\_PublishedWebsites\CS.Public.WebApplication\"
$webdestinationpath = "\\vmwebstg\WebRoot\CreditSolutions\"
new-item -force -path $webdestinationpath -itemtype "directory"
get-childitem $webdestinationpath | remove-item -force -recurse
get-childitem $websourcepath | copy-item -force -recurse -destination $webdestinationpath
$configFile = $webdestinationpath + "web.development.config"
remove-item $configFile -force
$configFile = $webdestinationpath + "web.staging.config"
$configFileDest = $webdestinationpath + "web.config"
move-item $configFile $configFileDest -force
So, how can I do this?
First add the Get-tfs function to your script:
function get-tfs (
[string] $serverName = $(Throw 'serverName is required')
)
{
# load the required dll
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")
$propertiesToAdd = (
('VCS', 'Microsoft.TeamFoundation.VersionControl.Client', 'Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer'),
('WIT', 'Microsoft.TeamFoundation.WorkItemTracking.Client', 'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore'),
('BS', 'Microsoft.TeamFoundation.Build.Common', 'Microsoft.TeamFoundation.Build.Proxy.BuildStore'),
('CSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.ICommonStructureService'),
('GSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.IGroupSecurityService')
)
# fetch the TFS instance, but add some useful properties to make life easier
# Make sure to "promote" it to a psobject now to make later modification easier
[psobject] $tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)
foreach ($entry in $propertiesToAdd) {
$scriptBlock = '
[System.Reflection.Assembly]::LoadWithPartialName("{0}") > $null
$this.GetService([{1}])
' -f $entry[1],$entry[2]
$tfs | add-member scriptproperty $entry[0] $ExecutionContext.InvokeCommand.NewScriptBlock($scriptBlock)
}
return $tfs
}
Next, instantiate the TFS object
$tfs = get-tfs http://YourTfsServer:8080
Then find the builds with a build quality of "Staging"
$builds = $tfs.BS.GetListOfBuilds("Test Project", "TestBuild") |
where {$_.BuildQuality -eq "Staging"}
Finally, update the quality of these builds
foreach ($build in $builds) { $tfs.BS.UpdateBuildQuality($build.BuildUri, "Rejected") }
(I haven't run this script yet, but you should be able to get it going without troubles)
More info on my blog: Using the Team Foundation Object Model with PowerShell
One last advice, if you update the Build quality from within the script that is running from TfsDeployer, you could end up with 2 script running at the same time if you have a mapping for the Staging --> Rejected transition!
This is not the full answer as I don't have much knowledge of TFSDeployer or indeed PowerScript. However the .NET API for Team Build is able to do this. You want to get hold of the IBuildDetail for the build. The easiest way to get this is if you have the BuildUri (which it sounds like you might) in which case a call to IBuildServer.GetBuild should get you the build you are interested in.
IBuildServer also has the QueryBuilds methods which you would be able to call to find the builds that are of interest to you, you would then set the Quality property on the IBuildDetails that you wanted to change, remembering to call the Save() method on each one.
Hope that gives you a start - sorry it isn't a more complete answer.

Resources