Visual Studio 2008 と Visual Source Safe(VSS)のバインド情報を取得する。

サンプルコード

Visual Studio 2008 と Visual Source Safe はチェックイン・チェックアウトの操作をIDE上から実施できるように、プロジェクトとソリューションをVSSのプロジェクト(フォルダ)と対応付けして連携して処理ができるようにしています。このバインド情報は、ツールバーの「ソース管理」-[ソース管理の変更]から設定します。

今回は、このバインド情報を解析して整理した上で、以下のクラスに取り込む処理を記載します。

まずは、バインド情報がどのような形式でVisualStudioが記録しているかを見ていきます。調べてみると、バインド情報はソリューションファイル(.sln)とソースコードコントロールファイル(mssccprj.scc)に記録されています。mssccprj.sccファイルは、.slnと同じフォルダに存在します。

ソリューションファイル(GlobalSection(SourceCodeControl)セクションに該当情報が設定されています。)

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "VssTest", "VssTest\VssTest.vbproj", "{75C5AB46-8C4F-4B56-8456-826462859A43}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "VssTest2", "VssTest2\VssTest2.vbproj", "{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "WindowsApplication1", "WindowsApplication1\WindowsApplication1.vbproj", "{D7547178-6BD0-459C-A604-3963E72BD1D7}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "WindowsApplication2", "WindowsApplication2\WindowsApplication2.vbproj", "{8F29E98F-A144-428E-ADDD-E62E4B45B077}"
EndProject
Global


GlobalSection(SourceCodeControl) = preSolution
SccNumberOfProjects = 5
SccLocalPath0 = .
SccProjectUniqueName1 = VssTest2\\VssTest2.vbproj
SccProjectName1 = \u0022$/VssEraser/VssTest2\u0022,\u0020QAAAAAAA
SccLocalPath1 = VssTest2
SccProjectUniqueName2 = VssTest\\VssTest.vbproj
SccProjectName2 = \u0022$/VssEraser/VssTest\u0022,\u0020CAAAAAAA
SccLocalPath2 = VssTest
SccProjectUniqueName3 = WindowsApplication1\\WindowsApplication1.vbproj
SccLocalPath3 = .
SccProjectFilePathRelativizedFromConnection3 = WindowsApplication1\\
SccProjectUniqueName4 = WindowsApplication2\\WindowsApplication2.vbproj
SccLocalPath4 = .
SccProjectFilePathRelativizedFromConnection4 = WindowsApplication2\\
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{75C5AB46-8C4F-4B56-8456-826462859A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C5AB46-8C4F-4B56-8456-826462859A43}.Release|Any CPU.Build.0 = Release|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D671519E-B335-4AAD-AE3F-CB6B16CA48B9}.Release|Any CPU.Build.0 = Release|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7547178-6BD0-459C-A604-3963E72BD1D7}.Release|Any CPU.Build.0 = Release|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F29E98F-A144-428E-ADDD-E62E4B45B077}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

ソースコードコントロールファイル(mssccprj.scc)


SCC = This is a Source Code Control file

[VssTest.sln]
SCC_Aux_Path = "C:\Users\Administrator\Documents\vss"
SCC_Project_Name = "$/VssEraser", BAAAAAAA

後は上記の情報を抜き出して整理します。整理用のコードは以下となります。


Imports System.Collections.Generic
Imports System.Runtime.InteropServices

Public Class VssBindInformation

Public SolutionFullName As String = String.Empty
Public SolutionVssSpecPath As String = String.Empty
Public SolutionVssSpec As String = String.Empty
Public Projects As New Dictionary(Of String, VssProjectBindInfo)

Public Class VssProjectBindInfo
Public ProjectFullName As String = String.Empty
Public LocalPath As String = String.Empty
Public VssSpecPath As String = String.Empty
End Class

_
Private Shared Function GetPrivateProfileString(ByVal lpAppName As String, _
ByVal lpKeyName As String, _
ByVal lpDefault As String, _
ByVal lpReturnedString As System.Text.StringBuilder, _
ByVal nSize As Integer, _
ByVal lpFileName As String) As Integer
End Function

Public Sub New(ByVal solutionFileName As String)

Me.SolutionFullName = solutionFileName

' Solution 情報の抽出
Dim solutionFolder As String = System.IO.Path.GetDirectoryName(solutionFileName)
Dim solutionVssFile As String = System.IO.Path.Combine(solutionFolder, "mssccprj.scc")

If System.IO.File.Exists(solutionVssFile) = True Then

Dim sb As New System.Text.StringBuilder
sb.Capacity = 255

GetPrivateProfileString(solutionFileName, _
"SCC_Project_Name", _
String.Empty, _
sb, _
sb.Capacity, _
solutionVssFile)

Me.SolutionVssSpecPath = GetSolutionVssSpecPath(sb.ToString) + "/"
Me.SolutionVssSpec = GetSolutionVssSpecPath(sb.ToString) + "/" + solutionFileName

End If

' Project 情報の抽出
Dim lines As String() = System.IO.File.ReadAllLines(solutionFileName)

Dim isStart As Boolean = False
Dim sccSection As New Dictionary(Of String, String)
For i As Integer = 0 To lines.Length - 1

Dim line As String = lines(i)

If line.IndexOf("GlobalSection(SourceCodeControl)", StringComparison.CurrentCultureIgnoreCase) > 0 Then
isStart = True
Continue For
End If

If isStart = True AndAlso line.IndexOf("EndGlobalSection", StringComparison.CurrentCultureIgnoreCase) > 0 Then
Exit For
End If

If isStart = True Then

Dim key As String = String.Empty
Dim value As String = String.Empty

Dim splitPos As Integer = line.IndexOf("=")
If splitPos > 0 Then

key = line.Substring(0, splitPos).TrimEnd.Replace(vbTab, String.Empty)
value = line.Substring(splitPos + 1).TrimStart

sccSection.Add(key, value)

End If

End If

Next

If sccSection.ContainsKey("SccNumberOfProjects") = True Then

Dim projectCount As Integer = CInt(sccSection.Item("SccNumberOfProjects"))

For i As Integer = 0 To projectCount - 1

Dim projectKeyName As String = "SccProjectName" + i.ToString
Dim projectUniqueKeyName As String = "SccProjectUniqueName" + i.ToString
Dim projectRelativeKeyName As String = "SccProjectFilePathRelativizedFromConnection" + i.ToString

If ((sccSection.ContainsKey(projectKeyName) = True OrElse sccSection.ContainsKey(projectRelativeKeyName))) AndAlso _
(sccSection.ContainsKey(projectUniqueKeyName)) = True Then

Dim item As New VssProjectBindInfo

item.ProjectFullName = GetAbsolutePath(solutionFolder, sccSection.Item(projectUniqueKeyName).Replace("\\", "\"))
item.LocalPath = System.IO.Path.GetDirectoryName(item.ProjectFullName)

If sccSection.ContainsKey(projectKeyName) = True Then
item.VssSpecPath = GetVssSpecPath(sccSection.Item(projectKeyName))
Else
item.VssSpecPath = Me.SolutionVssSpecPath + sccSection.Item(projectRelativeKeyName).Replace("\\", String.Empty)
End If

Me.Projects.Add(item.ProjectFullName.ToLower, item)

End If

Next

End If

End Sub

Private Function GetAbsolutePath(ByVal basePath As String, ByVal relatePath As String) As String

Dim targetBasePath As String = basePath
If basePath.Length > 0 Then
If (basePath.Substring(basePath.Length - 1, 1) <> "\") AndAlso (basePath.Substring(basePath.Length - 1, 1) <> "/") Then
targetBasePath += "\"
End If
End If

Dim baseUri As New System.Uri(targetBasePath)
Dim absUri As New System.Uri(baseUri, relatePath)

Return absUri.LocalPath

End Function

Private Function GetSolutionVssSpecPath(ByVal sccProjectName As String) As String

Dim line As String = sccProjectName
Dim pos As Integer = 0

pos = sccProjectName.IndexOf(",")
If pos > 0 Then
line = line.Substring(0, pos)
End If

line = line.Replace("""", String.Empty)

Return line

End Function

Private Function GetVssSpecPath(ByVal projectKeyName As String) As String

Dim line As String = projectKeyName
Dim pos As Integer = 0

pos = projectKeyName.IndexOf(",")
If pos > 0 Then
line = line.Substring(0, pos)
End If

pos = projectKeyName.IndexOf("$")
If pos > 0 Then
line = line.Replace(line.Substring(0, pos), String.Empty)
End If

Return line

End Function

End Class