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

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.

Related

SSDT - How to change database object names per build configuration

The problem:
I want to manage my database(s) using one SSDT database project. in which I want to centralize database development and automate deployments (mainly stored procedures).
In a multi-tenant environment, where object names are preceded by company names,
example :
[dbo].[spu_COMPANY_NAME$Stored Procedure Name]
we have a central database in which we do our development and every time we publish, we do a 'Replace All' to the company name.
The SQLCMD variables won't do because they cannot be included inside object names.
Is there a way I can build so that for every build configuration I get tailored stored procedures during build/publish, I get a folder structure like this :
--Database.Project/
--bin/
--CompanyA(build.companyA.congig)/
--CompanyB(build.companyB.congig)/
As #PeterSchott mentioned in a previous comment, the solution is easily doable through Powershell scripts and Pre/Post build events in Visual Studio.
I'll post the answer here in case someone needed it for reference (or maybe in case I needed it)
First:
Prebuild powershell script, this one takes the project directory, and replaces all strings with 'COMPANY_NAME' to the actual company name depending on the build configuration, for all files with .sql extension in them.
Next:
The powershell script:
if ($args[0] -eq "Release"){
$company_name = "ReleaseCorp";
}
$dboDir = $args[1] + 'dbo\';
$sqlFiles = Get-ChildItem $dboDir -rec | Where-Object {($_.Extension -eq ".sql")}
foreach ($file in $sqlFiles)
{
echo $file;
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace "COMPANY_NAME", $company_name } |
Set-Content $file.PSPath
}
Post-build event does the inverse, only that this time it takes the actual company name for the configuration and puts it back to the token, for upcoming builds.
Worth noting that for Visual Studio to be able to run Powershell (x86) scripts, the following step is mandatory :
Run Windows Powershell (x86) in administrator mode, and run :
set-executionpolicy unrestricted
More info about this to be found in David Frette's informative blog post :
Creating Powershell pre-build and post-build events for Visual Studio projects

Powershell File Watcher Not Picking Up File Changes Made in Visual Studio

I have a powershell process that watches for SQL files (in a database project), and then triggers an action when a file changes. The basic setup is below.
$global:SQLFileWatcher = New-Object IO.FileSystemWatcher $rootFolder, $sqlFilter -Property #{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'LastWrite'
}
$onSqlCreated = Register-ObjectEvent $global:SQLFileWatcher Changed -SourceIdentifier SqlFileChanged -Action {
#Only called when a new file is added to DB project
Write-Host "HERE"
}
When making changes an existing file already created in Visual Studio Database Project, it's not triggering the file watcher as if the LastWrite time is not being updated. If I open up Notepad++ and edit, this works fine. Also if I add a new SQL file (to a database project), then it triggers the file watcher event on creation AND any updates on that file, which was the strangest part.
Why doesn't it update the file attributes on an existing file, only on new files? Or, a better question to ask may be: is there a better NotifyFilter setting to use instead of "LastWrite"?

ProjectItems.AddFromFile Adds File to Pending Changes

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
}
}

Remove TFS Connection From Solution

How to make solution as clean copy without mapping to TFS ? The problem is that this message shows when I am trying to open it. I want to open it as normal without TFS connection.
To completely remove TFS source control binding follow these two steps:
Go to your solution's folder, find and delete all files with *.vssscc and *.vspscc extensions.
Open your solution's .sln file in Notepad, and find & remove the GlobalSection(TeamFoundationVersionControl) section.
More details on reference Link
If you want to permanently and completely detach the solution from source control, then try the following:
Click the 'No' button to avoid connecting to TFS.
In the file menu, go to the source control options and clear the bindings. You'll specifically want File - Source Control - Advanced - Change Source Control...
Save the solution.
Next time you open the solution you won't be prompted to connect to TFS.
Edit the solution file and remove the following section from it. It won't be the same but will be similar.
Note:To edit the solution file go to the project folder then open the YouSolutionName.sln file with notepad.
GlobalSection(TeamFoundationVersionControl) = preSolution
SccNumberOfProjects = 2
SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
SccTeamFoundationServer = <YourTFSURL>
SccLocalPath0 = .
SccProjectUniqueName1 = .
SccLocalPath1 = .
EndGlobalSection
I don't have enough reputation to comment, but just wanted to add that Tabish's solution does in fact work correctly to completely remove the solution from source control, especially when the TFS server is not reachable for one reason or another (e.g. you downloaded a project that the author did not remove from their own source control before uploading).
However, to completely remove all traces of source control from the project and avoid the warnings that are noted in the other comments to that answer (e.g. "The mappings for the solution could not be found..."), you must also remove the following lines from every project file in the solution (apparently these used to be in the solution file in earlier versions of VS, but in VS2017 they are found in the project file for each project in the solution - e.g. [project].csproj):
SccProjectName = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
SccAuxPath = "x"
SccLocalPath = "xxx"
SccProvider = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Thanks to the marked answer and other comments here for pointing this out:
visual source safe - how to remove bindings from solution w/o opening in visual studio
Combining this with Tabish's answer seems to be the most complete method of manually removing a solution from source control.
To remove the binding you can use Visual Studio : Menu File / Source Control / Advanced / Change Source Control.
You can also do it yourself by removing any SCC... from sln and csproj.
If you often export source files, you can use ExportSrc. It has many options such as remove TFS binding (ON by default).
Most of the answers provided a solution, but I would rather use a solution provided by Visual Studio 2017.
On the Menu bar of Visual Studio, go to Team and select Disconnect from Team Foundation Server.
That's it.
I just inherited a collection of TeamFoundation projects following an M&A buyout and tech transfer. There were like 30+ solutions and a buttload of *.vssscc and *.vspscc files.
Based on everyone's input above, I wrote a PowerShell function to recurse a specified root folder, delete the files, then edit the solution files to remove the TeamFoundationVersionControl section.
Usage is Remove_TFSfiles "pathname" $booleanflag.
To see what files would be affected, use $false (uses -whatif):
Remove_TFSfiles "C:\MyDevFolder" $false
To actually delete those files, use $true:
Remove_TFSfiles "C:\MyDevFolder" $true
Here's the function:
Function Remove_TFSfiles {
param(
[string]$FolderName = $(throw "-FolderName is required."),
[bool]$RemoveFiles = $(throw "-RemoveFiles (either $true or $false) is required.")
)
$TFSExtensions = '*.vspscc', '*.vssscc'
if ($RemoveFiles) {
Get-ChildItem -path $FolderName -force -include $TFSExtensions -Recurse | Remove-Item -Force
# Now iterate through any solution files, and whack the TeamFoundationVersionControl section
Get-ChildItem -Path $FolderName -Filter "*.sln" -Recurse | ForEach-Object {
$slnFilename = $_.Fullname
Write-Host -NoNewline "Editing $slnFilename... "
$File = Get-Content $slnFilename -raw
$Filestr = [regex]::escape("" + $File + "")
# The regex escapes must themselves be meta-escaped, therefore "\(" becomes "\\" + "\(" = "\\\(". Did I mention I hate regex?
$Result = $Filestr -replace "\\tGlobalSection\\\(TeamFoundationVersionControl\\\).*?EndGlobalSection\\r\\n", ""
$result = [regex]::unescape($result)
Set-ItemProperty $slnFilename IsReadOnly $false
$result | Set-Content $slnFilename
Write-Host "Done"
}
Write-Host -f red "Finished actually removing files and editing *.sln files"
}
else {
Get-ChildItem -path $FolderName -force -include $TFSExtensions -Recurse | Remove-Item -WhatIf
Write-Host -f green "Finished pretending to remove files"
# Now iterate through any solution files, and whack the TeamFoundationVersionControl section
Get-ChildItem -Path $FolderName -Filter "*.sln" -Recurse | ForEach-Object {
$slnFilename = $_.Fullname
Write-Host "Not Editing $slnFilename"
}
}
}

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>

Resources