Powershell Wrapper for CLC

If you are having a problem using Vault, post a message here.

Moderator: SourceGear

Post Reply
everettmuniz
Posts: 21
Joined: Wed Sep 27, 2006 6:51 am

Powershell Wrapper for CLC

Post by everettmuniz » Tue Dec 13, 2011 4:16 pm

Just in case it's useful to anyone, this is the script we use to refresh solution dependencies that aren't a part of the actual VS.NET solution. If you use NuGet this could come in handy since NuGet stores the files in a folder called Packages off the solution dir but the packages dir is not a part of the VS.NET solution. I include this script in the project and setup an External Tool to call it to either refresh the packages folder (i.e. get latest) or add new packages to source control when I install a NuGet package (i.e. detect files to add).

Though I'm using it regularly I still consider it alpha because I haven't really put it through its paces. A few words on how it works...It searches for vault.exe and caches its location in a file called _vault.location in the same directory where the script lives. It assumes the vault client was installed under "program files". On subsequent calls it reads the _vault.location, checks that it's valid and re-locates it if not; otherwise it uses the cached path. It needs tested in 64 bit environment. It implements a method for adding missing files (similar to the detect files to add functionality from the client) but that too needs further testing as well. Most of the code below is for the detect files to add method. Use at your own risk ;-)

Code: Select all

[CmdletBinding()]
param (
    [parameter(Mandatory=$false)]
    [Alias("Host")]
    [ValidateNotNullOrEmpty()]
    [string]$sccHost="your host name here",
    
    [parameter(Mandatory=$true)]
    [Alias("User")]
    [ValidateNotNullOrEmpty()]
    [string]$sccUser,
    
    [parameter(Mandatory=$true)]
    [Alias("Password")]
    [ValidateNotNullOrEmpty()]
    [string]$sccPassword,

    [parameter(Mandatory=$false)]
    [Alias("Action")]
    [ValidateNotNullOrEmpty()]
    [string]$sccAction = "GET"
)

$ErrorActionPreference = "Stop"

function Test-64bit() 
{
  return ([IntPtr]::Size -eq 8)
}

function Get-ProgramFilesPath() 
{
    if (Test-64bit) 
    {
        $item = Get-Item "Env:ProgramFiles(x86)"
    }
    else 
    {
        $item = Get-Item "Env:ProgramFiles"
    }
    $item.Value
}

function Get-ScriptFolderPath
{   
    $Scope = 1
    $Directory = $null
    do
    {
        $Invocation = (Get-Variable MyInvocation -Scope $Scope).Value
        if ($Invocation.MyCommand.Path)
        {
            $Directory = (Split-Path $Invocation.MyCommand.Path)
        }
        $Scope += 1
    }
    while($Invocation -ne $null -and $Directory -eq $null)
    $Directory
}

function New-VaultClient([string]$sccHost, [string]$sccUser, [string]$sccPassword, [string]$sccRepository)
{
    $scriptFolderPath = Get-ScriptFolderPath
    $locationFilePath = Join-Path $ScriptFolderPath "_vault.location"
    $logFilePath = Join-Path $ScriptFolderPath "_vault.log"
    $exeFilePath = $null
    
    if (Test-Path $locationFilePath)
    {
        $exeFilePath = Get-Content $locationFilePath
    }

    if ($exeFilePath -eq $null -or !(Test-Path $exeFilePath))
    {
        $programFilesPath = Get-ProgramFilesPath
        
        Write-Host ("Looking for vault.exe under {0}." -f $programFilesPath)
        
        $exeFilePath = $programFilesPath | 
            gci -Include "*.exe" -Recurse | 
            ? { $_.Name -match "^vault.exe$" } | 
            % { $_.FullName } | 
            Select-Object -First 1

        Set-Content $locationFilePath $exeFilePath
    }

    if (Test-Path $logFilePath)
    {
        Clear-Content $logFilePath -Force
    }

    New-Object PSObject |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "_scriptFolderPath" -Value $scriptFolderPath |        
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "_locationFilePath" -Value $locationFilePath |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "_logFilePath" -Value $logFilePath |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "Host" -Value $sccHost |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "User" -Value $sccUser |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "Password" -Value $sccPassword |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "Repository" -Value $sccRepository |
        Add-Member -PassThru -MemberType NoteProperty -Force -Name "Executable" -Value $exeFilePath |        
        Add-Member -PassThru -MemberType ScriptMethod -Force -Name "Execute" -Value {
            param([string]$action, [object[]]$params)
            $result = [xml](& $this.Executable $action -host $this.Host -user $this.User -password $this.Password -repository $this.Repository @params)            
            if ($result.SelectSingleNode("/vault/result/success/text()").Value -ne "True") 
            {
                Write-Warning ("{0} failed with params {1}." -f $action, [string]::Join(" ", $params))
                $exception = $result.SelectSingleNode("/vault/error/exception/text()")
                if ($exception)
                {
                    Write-Warning "Details about the failure have been logged."
                    Add-Content $this._logFilePath ($exception.Value + "`r`n`r`n")
                }
            }
            $result
        } |
        Add-Member -PassThru -MemberType ScriptMethod -Force -Name "GetFolderDetails" -Value {
            param([ValidateNotNullOrEmpty()][string]$sccPath)
            $this.Execute("LISTFOLDER", @($sccPath))
        } |
        Add-Member -PassThru -MemberType ScriptMethod -Force -Name "GetLatest" -Value {
            param([ValidateNotNullOrEmpty()][string]$sccPath)

            $files = $this.GetFolderDetails($sccPath).SelectNodes("//file[@status]") | 
                % { Join-Path $_.SelectSingleNode("parent::folder/@workingfolder").Value $_.Name }

            $this.Execute("GET", @("-performdeletions", "-verbose", $sccPath)) | Out-Null
            $files
        } |
        Add-Member -PassThru -MemberType ScriptMethod -Force -Name "AddMissingFiles" -Value {
            param([ValidateNotNullOrEmpty()][string]$sccPath)

            $addedFolders = @()
            $details = $this.GetFolderDetails($sccPath);
            $details.SelectNodes("(//@workingfolder | //@name[parent::file])") |
                % { $_.value = $_.value.ToUpper() }
            $localFolder = $details.SelectSingleNode("/vault/folder/@workingfolder").Value
            $remoteFiles = $details.SelectNodes("//file") |
                % { Join-Path $_.SelectSingleNode("parent::folder/@workingfolder").Value $_.Name }                
            $localFiles = $localFolder |
                gci -Recurse |
                ? { !$_.PSIsContainer } |
                % { 
                    @{
                        DirectoryName = $_.DirectoryName.ToUpper()
                        FullName = $_.FullName.ToUpper()
                    } 
                }
            
            $localFiles | 
                ? { $remoteFiles -notcontains $_.FullName } |
                % {
                    $query = ("//folder[@workingfolder='{0}']" -f $_.DirectoryName)
                    $remoteFolder = $details.SelectSingleNode($query)                    
                    if ($remoteFolder)
                    {
                        $this.Execute("ADD", @("-commit", $remoteFolder.Name, $_.FullName)) | Out-Null
                        $_.FullName
                    }
                    else
                    {
                        # If this file's directory starts with the name of any 
                        # directory already added then we can skip it.
                        $directoryName = $_.DirectoryName      
                        if (-not ($addedFolders | ? { $directoryName.StartsWith($_)}))
                        {
                            while($remoteFolder -eq $null -and $directoryName -ne "")
                            {
                                $parentDirectoryName = Split-Path $directoryName
                                $query = ("//folder[@workingfolder='{0}']" -f $parentDirectoryName)
                                $remoteFolder = $details.SelectSingleNode($query)
                                if (!$remoteFolder)
                                {
                                    $directoryName = $parentDirectoryName
                                }
                            }

                            if ($remoteFolder)
                            {
                                $this.Execute("ADD", @("-commit", $remoteFolder.Name, $directoryName)) | Out-Null
                                $addedFolders += $directoryName
                                $directoryName
                            }
                        }
                    }
                }
        } 
}

$client = New-VaultClient $sccHost $sccUser $sccPassword "your repository name here"
switch -regex ($sccAction)
{
    "^GET$" {
        "Getting out-of-date files from vault..."
        $client.GetLatest("$/some/solution/path/Packages")
    }
    "^ADD$" {
        "Adding missing files to vault..."
        $client.AddMissingFiles("$/some/solution/path/Packages")
    }
    default {
        throw "Unknown action.  Expected GET or ADD."
    }
}
"Done"
Last edited by everettmuniz on Tue Dec 13, 2011 9:04 pm, edited 1 time in total.

Beth
Posts: 8550
Joined: Wed Jun 21, 2006 8:24 pm
Location: SourceGear
Contact:

Re: Powershell Wrapper for CLC

Post by Beth » Tue Dec 13, 2011 4:37 pm

Thank you for your post.

There's a line in your code that translated two characters to a smiley face. I'm reposting that one line here.

Code: Select all

return ([IntPtr]::Size -eq 8)
[/size]
Beth Kieler
SourceGear Technical Support

everettmuniz
Posts: 21
Joined: Wed Sep 27, 2006 6:51 am

Re: Powershell Wrapper for CLC

Post by everettmuniz » Tue Dec 13, 2011 9:07 pm

Thanks Beth. I updated the original post to use the code tags. I should have done that to begin with.

Post Reply