Home > Enterprise >  Azure Pipelines: How to do successful BITS-Transfer in Powershell task?
Azure Pipelines: How to do successful BITS-Transfer in Powershell task?

Time:02-05

Can't find the answer for this anywhere.

I need my pipeline to call a powershell script that has to do a BITS-Transfer multiple times. At first the script would always exit with this error:

Start-BitsTransfer : The operation being requested was not performed because the user has not 
logged on to the 
network. The specified service does not exist. (Exception from HRESULT: 0x800704DD)

Then, in the Powershell task, instead of calling the file, I changed it to do this inline script:

powershell -File $(Build.Repository.LocalPath)\installer\assembly.win10.x64.ps1 -Credential Get-Credential [Domain]\[Username]

Now the script runs, but I get this in my log in Azure Pipelines:

Microsoft Visual C   2015 Redistributable x64 : downloading...
Start-BitsTransfer : The operation being requested was not performed because the user has not 
logged on to the 
network. The specified service does not exist. (Exception from HRESULT: 0x800704DD)
At C:\agents\_work\3\s\installer\assembly.win10.x64.ps1:9 char:5
      Start-BitsTransfer -Source $Url -Destination $Target
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  CategoryInfo          : NotSpecified: (:) [Start-BitsTransfer], COMException
  FullyQualifiedErrorId : 
System.Runtime.InteropServices.COMException,Microsoft.BackgroundIntelligentTransfer.Management.NewBitsTransferCommand

Microsoft Visual C   2015 Redistributable x64 : download completed

The pipeline passes, but when I check the actual redistributable's .exe, it has a size of 0 bytes. So, it seems the folders are made and zipped, and the files are where they need to be, but the download didn't actually work. I also tested the powershell script on my machine locally, and there are no problems. How can I fix this?

Here is the relevant code. First three blocks are the download-related methods, then lastly is the method call:

function Get-FileSha256([string]$Src) {
  return (Get-FileHash "$Src" -Algorithm SHA256).Hash
}

function Download-File([string]$Comment, [string]$Url, [string]$Target) {
  Write-Host "$Comment : downloading..."
  New-Item -ItemType File -Path "$Target" -Force | Out-Null
  Import-Module BitsTransfer
  Start-BitsTransfer -Source $Url -Destination $Target
  Write-Host "$Comment : download completed"
}

function Download-FileIfNecessary([string]$Comment, [string]$Url, 
  [string]$Target, [string]$Sha256) {
  If (!(Test-Path $Target)) {
    Download-File "$Comment" "$Url" "$Target"
    return
  }

  $TargetSha256 = Get-FileSha256 $Target
  If ($TargetSha256 -eq $Sha256) {
    Write-Host "$Comment : already downloaded"
  }
  Else {
    Download-File "$Comment" "$Url" "$Target"
  }
}

Download-FileIfNecessary "Microsoft Visual C   2015 Redistributable x64" `
  "https://download.microsoft.com/download/6/A/A/6AA4EDFF-645B-48C5-81CC-ED5963AEAD48/vc_redist.x64.exe" `
  "$target_downloaded_vc_redist_dir\2015.v14.0.24215\vc_redist.x64.exe" `
  "da66717784c192f1004e856bbcf7b3e13b7bf3ea45932c48e4c9b9a50ca80965"

CodePudding user response:

I'm going to offer an alternative to using BITS, as BITS is designed to be used when the requestor is a local authenticated user, not by remote sessions, service accounts, or other non-interactive contexts. I've re-written
Download-File to make use of Invoke-WebRequest instead of Start-BitsTransfer:

Function Download-File {
  Param(
    [string]$Comment,
    [string]$Url,
    [string]$Target
  )

  Write-Host "$Comment : downloading..."

  # We need to check that the target folder exists later
  $targetFolder = Split-Path -Parent $Target

  # Prepare cmdlet arguments for splatting
  $iwrArgs = @{
    UseBasicParsing = $true
    Uri             = $Url
    OutFile         = $Target
  }

  $newDirArgs = @{
    ItemType = 'Directory'
    Path     = $targetFolder
  }

  # Create the target parent dir, if it doesn't exist
  if ( !( Test-Path -PathType Container $targetFolder ) ) {

    Write-Host "Creating directory $targetFolder"
    New-Item @newDirArgs | Out-Null
  }

  # Workaround progress bar performance bug with Invoke-WebRequest
  # (also affects Invoke-RestMethod) by disabling it
  $OldProgressPreference = $ProgressPreference
  $ProgressPreference = 'SilentlyContinue'

  # Use a try / finally block to guarantee the
  # original progress preference is changed back
  try {

    Invoke-WebRequest @iwrArgs -EA Stop
  }
  finally {

    $ProgressPreference = $OldProgressPreference
  }
}

I've commented the code if you're interested in what each piece is doing. I can clarify if there are any questions.

You should be able to drop this in place of your current Download-File definition. I would recommend this over trying hacky OS tricks to make BITS work in a context it's not designed to. BITS is also not recommended for use with critical automation, as it downloads can be suspended by the OS at any time (e.g. when updates are being downloaded).

Note that what I've said is true for remote accounts, but BITS should be safe to use from a scheduled task, just not from a Windows service (which the Azure Agent is).

  •  Tags:  
  • Related