3

So I have a Git repo in Azure DevOps, with a URL like

https://[$server]/tfs/[$company]/[$project]/_git/[$repoName]

And I can get to individual files in that repo by appending to that something like this:

?path=/[$folder]/[$fileName]

I am trying to use Powershell to download a specific file from this repo to the corresponding location and filename on my computer, like this:

$sourcePath = "https://[$server]/tfs/[$company]/[$project]/_git/[$repoName]?path=/[$folder]/[$fileName]&download=true"
$filePath = "C:\Documents\[$folder]\[$fileName]"
Invoke-RestMethod -Uri $sourcePath -Method Get -Headers @{Authorization=("Basic {0}" -f [$AuthInfo])} -OutFile $filePath

What this is doing is instead of replacing the local file with the file from the repo, it is replacing the contents of the local file with the contents of the response body. I find it odd that all the googling I've been doing says to do it this way, even though the Microsoft article on Invoke-RestMethod actually explains that this is what -OutFile does.

Note I've also tried Invoke-WebRequest....same thing.

So how do I download the actual file from the repo to the local destination I want (or replace the contents of the local destination file with the contents of the repo file)?

In addition, is there a way to specify which branch to get the file from? Maybe I should be using Git powershell commands with this as well somehow? All the other googling I've done about downloading from a git repo comes up with results about GitHub.

Thank you!

Andy
  • 616
  • 11
  • 32
  • 1
    Here's the git solution, but I'd be interested to see how to do it in powershell too- https://stackoverflow.com/questions/28375418/git-how-to-pull-a-single-file-from-a-server-repository-in-git – OwlsSleeping Apr 26 '21 at 20:20
  • Yeah, @OwlsSleeping - not only is that the Git way, but it looks like it's to download a file to where you have the repo cloned, instead of specifying a location to download it to (which I need to be able to do). Thanks, tho! – Andy Apr 26 '21 at 22:13
  • 1
    This isn't really a *Git* thing. Git is all about commits as contained in repositories. The fact that commits *hold* files (each commit being in part an archive of every file) is useful here but the way to *get* one file *from* a commit, on a server that has a Git repository, is up to that server. That's why you found all those GitHub-specific answers. You need an Azure-specific answer. – torek Apr 27 '21 at 00:50

2 Answers2

3

Use the rest api with the following template:

GET https://{tfs_url}/{collection_name}/{project}/_apis/git/repositories/{repositoryId}/items?path={path}

Check the documentation: Items - Get, Example - Download

I use the following code to copy files (with System.Net.WebClient) in build pipelines:

$user = ""
$token = $env:SYSTEM_ACCESSTOKEN
$teamProject = $env:SYSTEM_TEAMPROJECT
$orgUrl = $env:SYSTEM_COLLECTIONURI
$repoName = "REPO_NAME"

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))

    function InvokeGetFileRequest ($GitFilePath, $OutFilePath)
    {
        Write-Host "Download file" $GitFilePath "to" $OutFilePath
     
        $uriGetFile = "$orgUrl/$teamProject/_apis/git/repositories/$repoName/items?scopePath=$GitFilePath&download=true&api-version=6.1-preview.1"
    
       Write-Host "Url:" $uriGetFile
    
        $wc = New-Object System.Net.WebClient
        $wc.Headers["Authorization"] = "Basic {0}" -f $base64AuthInfo
        $wc.Headers["Content-Type"] = "application/json";
        $wc.DownloadFile($uriGetFile, $OutFilePath)
    }
Shamrai Aleksander
  • 13,096
  • 3
  • 24
  • 31
  • Hmmm...unfortunately that is doing the same thing - the local file's contents is getting replaced with the request's content. Thank you, though! – Andy Apr 27 '21 at 14:22
  • Could it literally be just because of the fact that my repo is in a "tfs" URL, instead of the kind of "repositories" URL as in your example? Like even though they are both technically Azure DevOps, they behave differently for things like this?? – Andy Apr 27 '21 at 14:33
  • @Andy For TFS should be the same `$tfs_coolection_url/$team_project_name/_apis/git/repositories/$repoName/items?scopePath=$GitFilePath&download=true&api-version=5.0`. Check the API version that support your TFS: https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-6.1#api-and-tfs-version-mapping. – Shamrai Aleksander Apr 27 '21 at 15:07
  • So the issue actually is with the URL I was using, which you clued me in on. Instead of "https://[$server]/tfs/[$company]/[$project]/_git/[$repoName]?path=/[$folder]/[$fileName]", I changed it to "https://[$server]/tfs/[$company]/[$project]/_apis/git/repositories/[$repoName]/items?path=/[$folder]/[$fileName]". And that worked! If you want to update your answer to specify the difference in URL, I'll mark it as the answer. Thank you!! – Andy Apr 27 '21 at 15:17
0

I needed to do this and what I saw here didn't work so I wrote my own:

param( 
    [Parameter(Mandatory=$true)] 
    [string] $GitFilePath,
    [Parameter(Mandatory=$true)] 
    [string] $OutFilePath,
    [Parameter(Mandatory=$true)] 
    [string] $RepoName,
    [string] $token,
    [string] $orgUrl,
    [string] $teamProject
)

if([String]::IsNullOrEmpty($token))
{
    if($env:SYSTEM_ACCESSTOKEN -eq $null)
    {
        Write-Error "you must either pass the -token parameter or use the BUILD_TOKEN environment variable"
        exit 1;
    }
    else
    {
        $token = $env:SYSTEM_ACCESSTOKEN;
    }
}


if([string]::IsNullOrEmpty($teamProject)){
    if($env:SYSTEM_TEAMPROJECT -eq $null)
    {
        Write-Error "you must either pass the -teampProject parameter or use the SYSTEM_TEAMPROJECT environment variable"
        exit 1;
    }
    else
    {
        $teamProject = $env:SYSTEM_TEAMPROJECT
    }
}

if([string]::IsNullOrEmpty($orgUrl)){
    if($env:SYSTEM_COLLECTIONURI -eq $null)
    {
        Write-Error "you must either pass the -orgUrl parameter or use the SYSTEM_COLLECTIONURI environment variable"
        exit 1;
    }
    else
    {
        $teamProject = $env:SYSTEM_COLLECTIONURI
    }
}

# Base64-encodes the Personal Access Token (PAT) appropriately  
$User='' 
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User,$token)));  
$header = @{Authorization=("Basic {0}" -f $base64AuthInfo)};  
#---------------------------------------------------------------------- 

Write-Host "Download file" $GitFilePath "to" $OutFilePath
     
$uriGetFile = "$orgUrl/$teamProject/_apis/git/repositories/$repoName/items?scopePath=$GitFilePath&download=true&api-version=6.1-preview.1"
    
Write-Host "Url:" $uriGetFile
    
$filecontent = Invoke-RestMethod -ContentType "application/json" -UseBasicParsing -Headers $header -Uri $uriGetFile
$filecontent | Out-File -Encoding utf8 $OutFilePath
Dan Hoeger
  • 31
  • 5