My development group has traditionally used TFS version control (TFVC) for our source control management. It hasn’t always been a great experience (especially when we had long lived branches outside of the MAIN branch) but we have been using it successfully for 6+ years.
I recently needed to move a couple of solutions from TFVC to Github and decided to automate the code preparation to make it an easier and more consistent process with PowerShell.
Pull from TFS
First we use the TFS REST API to pull the source code from TFS after asking the user for their credentials. The source is downloaded as a compressed zip file. We pull a fresh copy of the source from TFS so we don’t accidentally grab extra local files during the migration. This also allows us to compare our old application folder against this new Github-ready folder before we commit to compare the differences.
Function PrepareSourceForGithub {
Param(
[string]$tfsUrl,
[string]$tfsCollection = ‘defaultcollection’,
[string]$folderPath,
[string]$newRepoName)$sourceZip = “source.zip”
$sourceZipUrl = http://$tfsUrl/$tfsCollection/_apis/tfvc/items?path=$folderPath&api-version=1.0
Write-Host “Please provide you TFS credentials:”
$creds = Get-Credential
Write-Host ‘Downloading the source. This may take a few minutes …’Invoke-WebRequest $sourceZipUrl -Credential $creds -Headers @{‘Accept’=’application/zip’} -OutFile $sourceZip
UNZIP Source
So next we need to unzip the code to a local folder. I was actually surprised that PowerShell doesn’t have a zip cmdlets built in but it wasn’t too hard to figure out with the help of a few blog posts:
function UnzipFile ([string]$file, [string]$destination, [string]$subFolder) {
$shell_app=new-object -com shell.application
$filePath = Get-Item $file
$zip_file = $shell_app.namespace(“$($filePath.FullName)”)Get-Item $destination | Remove-Item -Recurse -Force
New-Item $destination -ItemType Directory
$destinationPath = Get-Item $destination
$dest = $shell_app.namespace($destinationPath.FullName)$dest.Copyhere($zip_file.items())Get-ChildItem -Path “$($destinationPath.FullName)/$subFolder/*” | Copy-Item -Destination $destinationPath.FullName -Recurse
Get-Item -Path “$($destinationPath.FullName)/$” | Remove-Item -Force -Recurse
}
Delete Files/Folders
Next we need to delete files/folders that are specific to TFS source control and other files we don’t want in Github. So we delete the compiled files in “bin” and “obj”. We also delete the packages folder at the solution level to get rid of all the binary files that Git doesn’t handle well.
Get-ChildItem -Path $sourcePath -Recurse -Directory -Filter ‘Packages’ | Get-ChildItem | Remove-Item -Force -Recurse
Get-ChildItem -Path $sourcePath -Recurse -Directory -Filter ‘bin’ | Remove-Item -Force -Recurse
Get-ChildItem -Path $sourcePath -Recurse -Directory -Filter ‘obj’ | Remove-Item -Force -Recurse
Get-ChildItem -Path $sourcePath -Recurse -File -Filter ‘*.vspscc’ | Remove-Item -Force
Get-ChildItem -Path $sourcePath -Recurse -File -Filter ‘*.csproj’ | ForEach-Object { RemoveSccNodesFromProjectFile($_.FullName)}
Get-ChildItem -Path $sourcePath -Recurse -File -Filter ‘*.sln’ | ForEach-Object { RemoveSccFromSolutionFile($_.FullName)}
NOTE – If you previously checked in your Nuget packages, you will need an alternate solution after moving to Git. You can restore via extensions to MSBuild, restore manually locally and/or add a restore step to your build pipeline.
Remove TFS References
There are references to TFS in both the project and solution files which need to be removed.
function RemoveSccNodesFromProjectFile ([string]$projectFile) {
$xml = Get-Content $projectFile
$xml.SelectNodes(“//*[starts-with(name(), ‘Scc’)]”) | ForEach-Object {$_.RemoveAll()}
$xml.Save($projectFile);}
function RemoveSccFromSolutionFile ([string]$solutionFile) {
$solutionContents = [Io.File]::ReadAllText($solutionFile)
#Get-Content $solutionFile
$solutionContents = $solutionContents -replace ‘GlobalSection\(TeamFoundationVersionControl\).*\s(.*Scc.*\s)*\sEndGlobalSection’, ”
Set-Content $solutionFile $solutionContents}
Add .git files
Technically we didn’t need to delete the files/folders above because they will be ignored via our .gitignore file. I still like to do that so we don’t have extra files hanging around locally. I’m copying the default file that is included when you create a new project Visual Studio and add Git source control from another Github project.
Invoke-WebRequest ‘https://raw.githubusercontent.com/rschiefer/SeleniumInDotNetCore2P2/master/.gitignore’ -OutFile “$sourcePath/.gitignore”
Invoke-WebRequest ‘https://raw.githubusercontent.com/rschiefer/SeleniumInDotNetCore2P2/master/.gitattributes’ -OutFile “$sourcePath/.gitattributes”
Summary
With this script we can make a single PowerShell method call with the TFS Server, Team Project Collection name, source path and new repo name to automate the preparation of the code.
PrepareSourceForGithub ‘tfsServer01p:8080/tfs’ ‘Acme’ ‘$/Fx/Services/FeatureFlags’ ‘acme.platform.shared.featureflag’
Next you would open the solution, build and run your tests to ensure everything is working correctly. Once everything is working you can run the git commands (init/commit) in this folder to commit it to Github.
Here is the full code.
Please leave a comment below if you found this post helpful or have further questions.
Happy Migrating!
Since 5.0 I think
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.archive/expand-archive
Nice, thanks for sharing this!