ProjectItems.AddFromFile Adds File to Pending Changes - visual-studio

As part of my nuget package, I have an install.ps1 powershell script that I am using to add a reference file to the project (a couple text documents) from the package's tools folder.
Everything is working great, except that when the files are referenced in a TFS solution, they are added to the Team Explorer Pending Changes. How can I remove them from pending changes (or keep them from ever showing up)? I don't want these checked into TFS, since the packages folder shouldn't be there in the first place.
Here's my install.ps1 script:
param($installPath, $toolsPath, $package, $project)
#Add reference text files to the project and opens them
Get-ChildItem $toolsPath -Filter *.txt |
ForEach-Object {
$projItem = $project.ProjectItems.AddFromFile($_.FullName)
If ($projItem -ne $null) {
$projItem.Properties.Item("BuildAction").Value = 0 # Set BuildAction to None
}
}

If you're using local workspaces (TFS 2012+) you can use the .tfignore file to exclude local folders and files from appearing in the Pending Changes page in Team Explorer.
You can configure which kinds of files are ignored by placing text file called .tfignore in the folder where you want rules to apply.
.tfignore file rules
The following rules apply to a .tfignore file:
- \# begins a comment line
- The \* and ? wildcards are supported.
- A filespec is recursive unless prefixed by the \\ character.
- ! negates a filespec (files that match the pattern are not ignored)
.tfignore file example
######################################
# Ignore .cpp files in the ProjA sub-folder and all its subfolders
ProjA\*.cpp
#
# Ignore .txt files in this folder
\*.txt
#
# Ignore .xml files in this folder and all its sub-folders
*.xml
#
# Ignore all files in the Temp sub-folder
\Temp
#
# Do not ignore .dll files in this folder nor in any of its sub-folders
!*.dll
Details: https://www.visualstudio.com/docs/tfvc/add-files-server#customize-which-files-are-ignored-by-version-control

I finally figured out how to do it using tf.exe. Calling tf vc undo with the full filename will undo pending changes for those files. And if the folder isn't tied to TFS, no harm done. It just continues on.
This implementation does require VS 2015 to be installed (due to the hardcodes path to the IDE folder), so I'm looking for a better way to obtain the IDE path of the currently loaded IDE. For now though, this solves my current issue.
param($installPath, $toolsPath, $package, $project)
$idePath = "$env:VS140COMNTOOLS..\IDE"
$tfPath = "$idePath\tf.exe"
Get-ChildItem $toolsPath -Filter *.txt |
ForEach-Object {
$projItem = $project.ProjectItems.AddFromFile($_.FullName)
If ($projItem -ne $null) {
$projItem.Properties.Item("BuildAction").Value = 0 # Set BuildAction to None
$filename = $_.FullName
& $tfPath vc undo `"$filename`" # Remove File from TFS Pending Changes, as AddFromFile can automatically add it
}
}

Related

Visual studio 2015 tfs add files to source control

I have the following issue-
On Team Explorer - Pending Changes there is an option that automatically detects added files to folders that are under source control. The problem is that more than 50,000 files are detected.
Is there any way to edit this list? to remove items I don't care about so it will be relevant when I do have files I want to add?
(I know I can add items in the Source Control but I want to make this option usable)
You can click the 'Detected' link to pop up the "Promote Candidate Changes" dialog, then select the files you want to check in to promote.
If you're using local workspaces, you can add a .tfignore file to ignore the files which you don't want to be detected in source control. eg: ignore by file extension .txt, then all the .txt files will be ignored in source control. They will not be detected.
Please see Customize which files are ignored by version control for details.
Please note that with TFVC you need to put .tfignore in every solution root.
.tfignore file rules The following rules apply to a .tfignore file:
# begins a comment line
The * and ? wildcards are supported.
A filespec is recursive unless prefixed by the \ character.
! negates a filespec (files that match the pattern are not ignored)
.tfignore file example
######################################
# Ignore .cpp files in the ProjA sub-folder and all its subfolders
ProjA\*.cpp
#
# Ignore .txt files in this folder
\*.txt
#
# Ignore .xml files in this folder and all its sub-folders
*.xml
#
# Ignore all files in the Temp sub-folder
\Temp
#
# Do not ignore .dll files in this folder nor in any of its sub-folders
!*.dll

Copy files based on existence of other files (Windows)

I have a folder of images in jpg format called "finalpics" and also another folder ("sourcepics") which has several subfolders containing RAW files in various formats.
I need a script (batch file?) that will copy all the files from "sourcepics" and its subfolders to another folder ("sourcefinal") only if that file exists in "finalpics".
As an example:
"finalpics" contains files called mypic1.jpg, mypic2.jpg, mypic3.jpg.
"sourcepics" contains files called mypic1.dng, mypic2.psd, mypic3.cr2, yourpic1.dng, yourpic2.psd, yourpic3.cr2.
I'd want the script to copy the 'mypic' files but not the 'yourpic' files to "sourcefinal".
There's over a thousand jpgs in "finalpics" but probably 40,000 files in the various subfolders of "sourcepics".
Hope that makes sense.
Thanks for looking.
I think this PowerShell code will do what you're after; it will copy files of the same name (ignoring file extension) from "SourcePics" to "SourceFinal" if they exist in FinalPics:
# Define your folder locations:
$SourcePicsFolder = 'C:\SourcePics'
$FinalPicsFolder = 'C:\FinalPics'
$SourceFinalFolder = 'C:\SourceFinal'
# Get existing files into arrays:
$SourcePics = Get-ChildItem -Path $SourcePicsFolder -Recurse
$FinalPics = Get-ChildItem -Path $FinalPicsFolder -Recurse
# Loop all files in the source folder:
foreach($file in $SourcePics)
{
# Using the basename property (which ignores file extension), if the $FinalPics
# array contains a basename equal to the basename of $file, then copy it:
if($FinalPics.BaseName -contains $file.BaseName)
{
Copy-Item -Path $file.FullName -Destination $SourceFinalFolder
}
}
Note: There is no filtering based on file type (e.g. it will copy all files). Also, if your 'SourcePics' folder has two images of the same filename but in different subfolders, and a file of this name also exists in 'FinalPics', then you may get an error about file already existing when it tries to copy for the second time. To overwrite, use the -Force parameter on the Copy-Item command.
I tested the above code with some .dng files in 'SourcePics' and .jpg files in 'FinalPics' and it worked (ignoring the yourpic files).

List files that are in the TFS repository but not in the Visual Studio solution

We have big solution(300+ projets), which is evolving since a long time(7 years), and over the time, there has been a lot of refactoring(projects removed, moved, ...). We did notice that sometimes when some projects are deleted from the solution, they are not deleted from the solution, and also that we have some leftover files.
We would like to find all those kind of files in order to delete most of them(not all).
Is there a way to list all those files?
(We have Visual Studio 2015 enterprise)
Check this PowerShell script from this case, which should do what you are looking for. It parses the project file to get the included code files. Then it compares that list to the actual files on disk. The remaining files are your unused/obsolete files.
The script can either delete the unused files from disk or pend them as deletes in TFS.
<#
.SYNOPSIS
Find and process files in a project folder that are not included in the project.
.DESCRIPTION
Find and process files in a project folder that are not included in the project.
Options to delete the files or to add them as pending deletes for TFS. Use TF.exe to pend the deletes and start the check-in process for the files.
This is necessary when trying to delete files that are not currently included in a Visual Studio project.
.PARAMETER Project
The path/name for the project file.
.PARAMETER VsVersion
The Visual Studio version (10, 11, 12). Used to locate the tf.exe file.
.PARAMETER DeleteFromDisk
Just delete the files from disk. No interaction with any source control.
.PARAMETER TfsCheckin
After pending the deletes, open the check-in dialog.
#>
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[string]$Project,
[Parameter(Mandatory=$false)]
[ValidateRange(10,12)]
[int] $VsVersion = 12,
[switch]$DeleteFromDisk,
[switch]$TfsCheckin
)
$ErrorActionPreference = "Stop"
$tfPath = "${env:ProgramFiles(X86)}\Microsoft Visual Studio $VsVersion.0\Common7\IDE\TF.exe"
$projectPath = Split-Path $project
if($Project.EndsWith("csproj"))
{
$fileType = "*.cs"
}
else
{
$fileType = "*.vb"
}
$fileType
$projectFiles = Select-String -Path $project -Pattern '<compile' | % { $_.Line -split '\t' } | `
% {$_ -replace "(<Compile Include=|\s|/>|["">])", ""} | % { "{0}\{1}" -f $projectPath, $_ }
Write-Host "Project files:" $projectFiles.Count
$diskFiles = gci -Path $path -Recurse -Filter $fileType | % { $_.FullName}
Write-Host "Disk files:" $diskFiles.Count
$diff = (compare-object $diskFiles $projectFiles -PassThru)
Write-Host "Excluded Files:" $diff.Count
#create a text file for log purposes
$diffFilePath = Join-Path $projectPath "DiffFileList.txt"
$diff | Out-File $diffFilePath -Encoding UTF8
notepad $diffFilePath
#just remove the files from disk
if($DeleteFileOnly)
{
$diff | % { Remove-Item -Path $_ -Force -Verbose}
}
else #TFS options
{
#this will add the files as pending deletes in TFS (awaiting check-in)
$diff | % {
[Array]$arguments = #("delete", "`"$_`"")
& "$tfPath" $arguments
}
if($Checkin)
{
#start the check-in process for the pending deletes
[Array]$arguments = "checkin", "/recursive", "$projectPath"
& $tfPath $arguments
}
}
Also, other community members expend this script and share the scripts at:
#Marc Climent: I used this script to create a more detailed one that includes other types of files and does not use TFS: https://gist.github.com/mcliment/d9008a9288cea9d088af
#mikesigs: I too used this file as well as #MarcCliment's to create yet another PowerShell script that takes a .sln file instead of a single proj file. It deletes all files excluded from all projects in the provided solution: https://gist.github.com/mikesigs/3512dbccc1767d447977#file-deleteexcludedfiles-ps1

How can I force files in my project so that they always copy to output directory

I am using a visual studio c# library project to contain static resources that are needed as deployment artifacts. (in my case SQL files that are run with a combination of RoundhousE and Octopus deploy). By convention all files in the project must have their properties set so that the "Build action" is "Content" and "Copy to output directory" is "Copy always".
If someone on the team adds a file but forgets to set these properties we see deployment errors. This is usually picked up in an internal environment, but I was hoping to find a way to enforce this in the CI build.
So is there a way to either fail the build or better still override these properties during the build with an MS Build task? Am I tackling this the wrong way? Any suggestions welcomed.
You are going to have to parse the project files and check for Content without CopyToOutputDirectory set to Always, I doubt there is another way.
That can be done using whatever scripting language you want, or you could even write a small C# tool that uses the classes from the Microsoft.Build.Evaluation namespace. Here is a possible PowerShell implementation - the hardest part is getting the regexes right. First one checks for Content without any metadata, second one for Content where CopyToOutputDirectory does not start with "A" (which I assume should be "Always", no idea how to match that whole word).
FindBadContentNodes.ps1 :
param([String]$inputDir)
Function FindBadContent()
{
$lines = Get-Content $input
$text = [string]::Join( "`n", $lines )
if( $text -match "<Content Include.*/>" -Or
$text -match "<Content Include.*`n\s*<CopyToOutputDirectory>[^A]\w*<.*" )
{
"Found file with bad content node"
exit 1
}
}
Get-ChildItem -Recurse -Include *.csproj -Path $inputDir | FindBadContent
Call this from MsBuild:
<Target Name="FindBadContentNodes">
<Exec Command="Powershell FindBadContentNodes.ps1 -inputDir path\to\sourceDir"/>
</Target>
Note you mention or better still override these properties during the build. I'd stay away from such a solution: you're just burying the problem and relying on the CI to produce correct builds, so local builds using just VS would not be the same. Imo making the build fail is better, especially since most CI systems have a way of notifying the developper that is responsible anyway so the fix should be applied quickly.
Another possibility would be to have the CI apply the fix and then commit the changes so at least everyone has the correct version.
IIRC there is a way in Visual Studio to set a file extension to do certain things on default, much like .config files will always set to content and copy to output directory.
So one could do the same with .sql files (and other files that they would want to be set up this way). A quick search brought me to this: http://blog.andreloker.de/post/2010/07/02/Visual-Studio-default-build-action-for-non-default-file-types.aspx
The relevant parts:
The default build action of a file type can be configured in the
registry. However, instead of hacking the registry manually, we use a
much better approach: pkgdef files (a good article about pkgdef
files). In essence, pkdef are configuration files similar to .reg
files that define registry keys and values that are automatically
merged into the correct location in the real registry. If the pkgfile
is removed, the changes are automatically undone. Thus, you can safely
modify the registry without the danger of breaking anything – or at
least, it’s easy to undo the damage.
Finally, here’s an example of how to change the default build action
of a file type:
1: [$RootKey$\Projects{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\FileExtensions.spark]
2: "DefaultBuildAction"="Content" The Guid in the key refers to project type. In this case, “{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}” means “C# projects”. A rather comprehensive list of project type guids can be found here. Although it does not cover Visual Studio 2010 explicitly, the Guids apply to the current version as well. By the way, we can use C# as the project type here, because C# based MVC projects are in fact C# projects (and web application projects). For Visual Basic, you’d use “{F184B08F-C81C-45F6-A57F-5ABD9991F28F}” instead.
$RootKey$ is in abstraction of the real registry key that Visual
Studio stores the configuration under:
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config (Note:
Do not try to manually edit anything under this key as it can be
overwritten at any time by Visual Studio).
The rest should be self explanatory: this option sets the default
build action of .spark files to “Content”, so those files are included
in the publishing process.
All you need to do now is to put this piece of text into a file with
the extension pkgdef, put it somewhere under
%PROGRAMFILES(x86)%\Microsoft Visual Studio
10.0\Common7\IDE\Extensions (on 64-bit systems) or %PROGRAMFILES(x86)%\Microsoft Visual Studio
10.0\Common7\IDE\Extensions (on 32-bit systems) and Visual Studio will load and apply the settings automatically the next time it starts. To
undo the changes, simply remove the files.
Finally, I’ve attached a bunch of pkgdef files that are use in
production that define the “Content” default Build Action for C# and
VB projects for .spark, .brail, .brailjs and .less files respectively.
Download them, save them somewhere in the Extensions folder and you’re
good to go.
The author also says that he built a utility to help do all of this for you:
http://tools.andreloker.de/dbag
Expanding on #stijn answer, instead of using regex it is far easier to use native xml parsing.
Here is my proposed file, it also supports the ability to customize which files are evaluated by using a regex on the filename only.
param([String]$Path, [string]$IncludeMatch, [switch]$AllowPreserve)
Function Test-BadContentExists
{
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string[]]$Path,
[string]$IncludeMatch,
[switch]$AllowPreserve
)
[xml]$proj = Get-Content -Path $Path
$ContentNodes = ($proj | Select-Xml "//Content|//n:Content" -Namespace #{n='http://schemas.microsoft.com/developer/msbuild/2003'}).Node
if (![string]::IsNullOrEmpty($IncludeMatch)) {
$ContentNodes = $ContentNodes | Where-Object -Property Include -Match $IncludeMatch
}
#remove the always nodes
$ContentNodes = $ContentNodes | Where-Object -Property CopyToOutputDirectory -ne 'Always'
#optionally remove the preserve nodes
if ($AllowPreserve) {
$ContentNodes = $ContentNodes | Where-Object -Property CopyToOutputDirectory -ne 'PreserveNewest'
}
if($ContentNodes)
{
write-output "Found file with bad content node:"
write-output ($ContentNodes | Select-Object Include,CopyToOutputDirectory | sort Include | Out-String)
exit 1
}
}
[hashtable]$Options = $PSBoundParameters
[void]$Options.Remove("Path")
Get-ChildItem -Recurse -Include *.csproj -Path $Path | Test-BadContentExists #Options
and calling it, with parameter:
<Target Name="FindBadContentNodes">
<Exec Command="Powershell FindBadContentNodes.ps1 -inputDir path\to\sourceDir -IncludeMatch '^Upgrade.*\.(sql|xml)$'"/>
</Target>
I ended up using a pre-build event instead and put this ps1 file in my solution directory so i could use it with multiple projects.
echo "Build Dir: %cd%"
echo "Sol Dir: $(SolutionDir)"
echo "Proj Dir: '$(ProjectDir)"
echo.
Powershell -NoProfile -Command "& '$(SolutionDir)\FindBadContentNodes.ps1' -Path '$(ProjectDir)' -IncludeMatch '^Upgrade.*\.(sql|xml)$'"
example build output:
1> "Build Dir: C:\Source\RPS\MRM BI\MRMBI-Setup\MRMBI-Schema\bin\Debug"
1> "Sol Dir: C:\Source\RPS\MRM BI\MRMBI-Setup\"
1> "Proj Dir: 'C:\Source\RPS\MRM BI\MRMBI-Setup\MRMBI-Schema\"
1>
1> Found file with bad content node:
1>
1> Include CopyToOutputDirectory
1> ------- ----------------------
1> Upgrades\V17.09\myfile1.sql
1> Upgrades\V20.05\myfile2.sql PreserveNewest
1>

How to get the IDE to update solution explorer window after adding project items in nuget ps?

During nuget install I give the user a command they can run. This command basically scans some files and creates some code templates and then inserts them into the current project. This works just fine - except for the fact that Solution Explorer does not update its tree view with the new files. I know this works because I can unload and reload the project file and the files are there.
In case it helps, here is the code I use to add the files to the project - the second function is what the user actually calls.
function add-to-project ($itemType, $project)
{
process
{
$bogus = $project.Xml.AddItem($itemType, $_)
}
}
# Parse a file
function Write-TTree-MetaData ($Path = $(throw "-Path must be supplied"))
{
$p = Get-Project
Write-Host "Inserting the results of the parsing into project" $p.Name
$ms = Get-MSBuildProject
$destDir = ([System.IO.FileInfo] $p.FullName).Directory
# Run the parse now
CmdTFileParser -d $destDir.FullName $Path
# Now, attempt to insert them all into the project
$allFiles = Get-ChildItem -Path $destDir.FullName
$allFiles | ? {$_.Extension -eq ".ntup"} | add-to-project "TTreeGroupSpec" $ms
$allFiles | ? {$_.Extension -eq ".ntupom"} | add-to-project "ROOTFileDataModel" $ms
# Make sure everything is saved!
$ms.Save()
$p.Save()
}
This code causes a funny dialog to pop up - "The project has been modified on disk - please reload" - and hopefully the user will reload, and then the files show up correctly... But it would be nice to avoid that and just have the script cause whatever is needed to happen. Perhaps I have to figure out how to unload and re-load the project?
What do I need to do to force solution explorer to update? Many thanks!
By using the MSBuild project you are bypassing Visual Studio and directly updating the MSBuild project file on disk. The easiest way to get Visual Studio to update the Solutions Explorer window is to use the Visual Studio project object instead which you get from the Get-Project command.
Below is a simple example which adds a file to the solution and changes its ItemType to be ROOTFileDataModel. The example assumes you have a packages.config file in your project's root directory which is not currently added to the project so it is not showing in Solution Explorer initially.
# Get project's root directory.
$p = Get-Project
$projectDir = [System.IO.Path]::GetDirectoryName($p.FileName)
# Add packages.config file to project. Should appear in Solution Explorer
$newFile = [System.IO.Path]::Combine($projectDir, "packages.config")
$projectItem = $p.ProjectItems.AddFromFile($newFile)
# Change file's ItemType to ROOTFileDataModel
$itemType = $projectItem.Properties.Item("ItemType")
$itemType.Value = "ROOTFileDataModel"
# Save the project.
$p.Save()
The main Visual Studio objects being used here are the Project, ProjectItem and the ProjectItems objects. Hopefully the above code can be adapted to your specific requirements.

Resources