Search code examples
windowspowershellwindows-servicespackermicrosoft-bits

How to download a file using BITS in a Packer provisioner?


I'm writing a provisioning script in PowerShell for a Packer-built Windows image on a CI pipeline. This process involves downloading several large files. I'm under the impression that BITS is faster than Invoke-WebRequest, so I've decided to use BITS to asynchronously download these large files.

The problem is that BITS will only process jobs for users that are interactively logged on...

BITS transfers files only when the job's owner is logged on to the computer (the user must have logged on interactively). BITS does not support the RunAs command.

...unless the job was submitted by a service account.

You can use BITS to transfer files from a service. The service must use the LocalSystem, LocalService, or NetworkService system account. These accounts are always logged on; therefore, jobs submitted by a service using these accounts always run.

But even then, there's a wrinkle:

If a service running under a system account impersonates the user before calling BITS, BITS responds as it would for any user account (for example, the user needs to be logged on to the computer for the transfer to occur).

This is an issue because the provisioning script runs as the Administrator account, which is not a service account and therefore must be logged in interactively to use BITS. This happens to be Packer's behavior, so I can't change this. I'm wrong, I can change this. See my final answer. How can I do the following in one PowerShell script?

  • Submit a BITS job as Administrator using a service account's credentials. I assume I need to pass something in to Start-BitsTransfer's -Credential parameter?
  • Store the BITS job in a local variable (jobs will be started at different places in the script)
  • Await the completion of the BITS job so I can start using the file I downloaded (jobs will be awaited at different places in the script)

Solution

  • It turns out there's a solution to this, although it's specific to Packer. I didn't mention much about my use of it because I didn't think it was that important.

    Contrary to my initial belief, Packer's PowerShell provisioner lets you run the provisioning script with elevated privileges as any user...

    • elevated_user and elevated_password (string) - If specified, the PowerShell script will be run with elevated privileges using the given Windows user.
    provisioner "powershell" {
       elevated_user = "Administrator"
       elevated_password = build.Password
    }
    

    ...including service users.

    If you specify an empty elevated_password value then the PowerShell script is run as a service account. For example:

    provisioner "powershell" {
      elevated_user = "SYSTEM"
      elevated_password = ""
    }
    

    After adjusting my Packer template's provisioner block accordingly, I can now confirm that Start-BitsTransfer and friends work as expected. No need to pass complicated arguments or play tricks with sessions.