0

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)
JesseTG
  • 2,025
  • 1
  • 24
  • 48
  • 1
    I think you might be reading something into the ambiguous term "Service Accounts" here - the documentation is talking about the security context of a _Windows Service_ configured to log on with a local system account - there are no communicable credentials you can pass to `Start-BitsTransfer -Credential`, you'd need to wrap your whole script in an actual Windows Service - this can't feasibly be done in a single self-contained script (not without a LOT of code and/or environmental side effects at least) – Mathias R. Jessen Apr 09 '21 at 23:32

2 Answers2

1

You could use psexec to run a secondary script with SYSTEM rights by the administrator content and have the primary script identify the exit code of the psexec process to confirm it has successfully executed.

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.1

https://learn.microsoft.com/en-us/sysinternals/downloads/psexec#:~:text=PsExec%20is%20a%20light-weight,to%20manually%20install%20client%20software.

https://weblogs.asp.net/soever/returning-an-exit-code-from-a-powershell-script

alexzelaya
  • 255
  • 1
  • 7
  • Thank you, I'll give this a try to see if it works. – JesseTG Apr 10 '21 at 16:27
  • Wait, actually, how does that solution not run afoul of this: "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)." – JesseTG Apr 10 '21 at 19:42
0

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.

JesseTG
  • 2,025
  • 1
  • 24
  • 48