Packer: Feature Request: Windows Scheduled Task provisioner

Created on 28 Jan 2016  ยท  15Comments  ยท  Source: hashicorp/packer

WinRM has some limitations, namely not being able to run a Windows Update session over a remote session. See documentation from Microsoft: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387288(v=vs.85).aspx

We've run into this for Chef and test-kitchen:
https://github.com/test-kitchen/test-kitchen/issues/876
https://github.com/test-kitchen/test-kitchen/issues/868

And the solution has been to use the chef-zero-scheduled-task provisioner: https://rubygems.org/gems/chef-zero-scheduled-task
There is also a WinRM-elevated Ruby Gem:
https://github.com/WinRb/winrm-elevated

I'd like to request that support for running scheduled tasks (or an option for the powershell script provisioner to run as a scheduled task) for Packer, as this will allow us to use Packer to patch a windows system during the build pipeline.

I'm sure that using packer to update a windows system will still be fraught with many issues (multiple reboots, etc), but at least this will get us moving in the right direction.

enhancement windows-guest

Most helpful comment

Having everything run as elevated is one approach. Seems to require the least amount of refactoring, but maybe not desirable from a least privileged standpoint.

Again looking at the way vagrant does it there's an option on the config that gets passed into the communicator (see: https://github.com/hashicorp/vagrant/blob/master/plugins/provisioners/chef/provisioner/chef_solo.rb#L202-L203) which is then used to choose whether or not to use the regular or elevated shell (https://github.com/hashicorp/vagrant/blob/master/plugins/communicators/winrm/communicator.rb#L145). As someone mentioned above in terms of keeping the experience consistent between the two products it'd probably be better to go with this approach.

In terms of actually getting the option hooked up with packer we could maybe add a new Elevated property here (https://github.com/hashicorp/packer/blob/master/communicator/winrm/config.go) that would be used by the communicator to choose whether or not to run as elevated. I assume this would probably have a higher scope of impact since the provisioners may need to be refactored to take into account the new property.

All 15 comments

@icberg7 can you not use Elevated Powershell (https://www.packer.io/docs/provisioners/powershell.html#elevated_user)? Under the hood it uses a scheduled task. In fact, the original implementation was stolen from Vagrant which uses the Ruby WinRM library you mention.

@mefellows Thanks for the info; I didn't even know that was a feature. I've attempted to build an OpenStack snapshot and run a powershell elevated script to patch the windows box. It seemed to go ok, but now that I try to spin up an instance from the snapshot, our openstack instance is not having a fun time trying to start it. I'll follow up in the next day or two to confirm if this actually worked.
Thanks!

No worries @icberg7, love to hear the feedback.

I came across this problem too. I had the same chef cookbooks and configuration in vagrant and packer, and vagrant would suceed and packer would fail. Vagrant can run chef with msu's because the shell elevation is in the vagrant winrm communicator and not on a per provisioner basis (ie, all winrm commands can choose to be 'elevated'). It would appear in packer that this functionality is restricted to the powershell provisioner, as the packer winrm communicator itself does not call the escalate script.

As a work around, I'm going to re-implement the chef provisioner using powershell and the file provisioners, but it would be nice to have consistent behaviour across hashicorp products and much cleaner if I could just use the built in chef provisioner.

@drewsonne can you share please some code snippet about how you workaround chef provisioner winrm limitations

@jugatsu sure. But it ain't pretty. :-)

I have a folder of cookbooks where the dependencies are all defined by a Berksfile, and I target a single cookbook. I'm running on macos locally, so you may have to change the "shell-local" provisioners.

I have a PS script to install chef:

# files/chef_install.ps1
(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi')
Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait

My Chef config file:

# packer-chef-client/client.rb
node_name          "packer-chef-my_cookbook"
file_cache_path    "C:/chef/cache"
file_backup_path   "C:/chef/backup"
cookbook_path      ["C:/Windows/Temp/packer-chef-client/cookbooks"]
role_path          []
log_level          :info
log_location       STDOUT
verbose_logging    true

enable_reporting   false

encrypted_data_bag_secret nil

chef_zero.enabled  true
local_mode         true
node_path          ["C:/Windows/Temp/packer-chef-client/nodes"]

and then my Chef json file:

# packer-chef-client/my_chef_config.json
{
  "run_list": [
    "recipe[my_cookbook::my_recipe]"
  ]
}

And then I have the packer file itself:

"provisioners": [
    {
        "type": "shell-local",
        "command": "berks vendor -b '{{template_dir}}/../cookbooks/my_cookbook/Berksfile'"
    },
    {
        "type": "shell-local",
        "command": "rm '{{template_dir}}/cookbooks.zip'"
    },
    {
        "type": "shell-local",
        "command": "(cd '{{template_dir}}/../berks-cookbooks' && zip -r '{{template_dir}}/cookbooks.zip' *)"
    },
    {
        "type": "file",
        "source": "{{template_dir}}/cookbooks.zip",
        "destination": "C:\\Windows\\Temp\\cookbooks.zip"
    },
    {
        "type": "file",
        "source": "{{template_dir}}/packer-chef-client/",
        "destination": "C:\\Windows\\Temp\\packer-chef-client\\"
    },
    {
        "type": "powershell",
        "inline": [
            "Add-Type -AssemblyName System.IO.Compression.FileSystem",
            "[System.IO.Compression.ZipFile]::ExtractToDirectory(\"C:\\Windows\\Temp\\cookbooks.zip\", \"C:\\Windows\\Temp\\packer-chef-client\\cookbooks\")"
        ]
    },
    {
        "type": "powershell",
        "script": "{{template_dir}}/files/install_chef.ps1"
    },
    {
        "type": "powershell",
        "elevated_user": "Administrator",
        "elevated_password": "{{user `temp_admin_password`}}",
        "inline": [
            "C:\\opscode\\chef\\bin\\chef-client.bat --local-mode --no-color -c C:\\Windows\\Temp\\packer-chef-client\\client.rb -j C:\\Windows\\Temp\\packer-chef-client\\my_chef_config.json"
        ]
    },
]

Breaking those sections down...

  1. Locally generate a folder with all my dependent cookbooks

    {
        "type": "shell-local",
        "command": "berks vendor -b '{{template_dir}}/../cookbooks/my_cookbook/Berksfile'"
    },
    
  2. Locally zip and upload that collection of cookbooks (zipping improves upload time as there's no need for a single winrm connection per file upload). Packer will check to make sure files exist before doing anything, so you need to have at least a dummy file named cookbooks.zip. I delete that file before I create my actual archive.

    {
        "type": "shell-local",
        "command": "rm '{{template_dir}}/cookbooks.zip'"
    },
    {
        "type": "shell-local",
        "command": "(cd '{{template_dir}}/../berks-cookbooks' && zip -r '{{template_dir}}/cookbooks.zip' *)"
    },
    {
        "type": "file",
        "source": "{{template_dir}}/cookbooks.zip",
        "destination": "C:\\Windows\\Temp\\cookbooks.zip"
    },
    
  3. Upload my config and json files for chef

    {
        "type": "file",
        "source": "{{template_dir}}/packer-chef-client/",
        "destination": "C:\\Windows\\Temp\\packer-chef-client\\"
    },
    
  4. Unzip the cookbooks into a place defined in the chef config file.

    {
        "type": "powershell",
        "inline": [
            "Add-Type -AssemblyName System.IO.Compression.FileSystem",
            "[System.IO.Compression.ZipFile]::ExtractToDirectory(\"C:\\Windows\\Temp\\cookbooks.zip\", \"C:\\Windows\\Temp\\packer-chef-client\\cookbooks\")"
        ]
    },
    
  5. Install chef

    {
        "type": "powershell",
        "script": "{{template_dir}}/files/install_chef.ps1"
    },
    
  6. Run Chef. I have a userdata file in my ec2 builder, which sets a static password for the sake of build the AMI, so I pass that in as a variable here.

    {
        "type": "powershell",
        "elevated_user": "Administrator",
        "elevated_password": "{{user `temp_admin_password`}}",
        "inline": [
            "C:\\opscode\\chef\\bin\\chef-client.bat --local-mode --no-color -c C:\\Windows\\Temp\\packer-chef-client\\client.rb -j C:\\Windows\\Temp\\packer-chef-client\\my_chef_config.json"
        ]
    },
    

I think I got all the chef parts in there. Let me know if you have any issues!

Awesome. Thanks a lot for sharing. it's a little bit complicated :)

@jugatsu it is complicated, yes. But I couldn't find a nicer way.

If you get something cleaner, please do let me know. It also makes me appreciate what vagrant and packer do under the hood and what I don't usually have to worry about.

Yeah. Having oportunity to run chef-client via scheduled task by packer would we big win.

Can this just be solved at the communicator level the same way vagrant is? This seems like a pretty good start: https://github.com/mefellows/winrm-powershell

If the fix is implemented in the communicator then all provisioners would get the benefit of the fix right? Plus the one off elevation logic could be removed from the PowerShell provisioner.

Are there concerns with implementing this into the communicator instead of the specific provisioner?

Are you suggesting we have the winrm communicator run all scripts with elevated privileges? If not, how do you think we should tell the winrm communicator what to run as an elevated script and what not to?

Having everything run as elevated is one approach. Seems to require the least amount of refactoring, but maybe not desirable from a least privileged standpoint.

Again looking at the way vagrant does it there's an option on the config that gets passed into the communicator (see: https://github.com/hashicorp/vagrant/blob/master/plugins/provisioners/chef/provisioner/chef_solo.rb#L202-L203) which is then used to choose whether or not to use the regular or elevated shell (https://github.com/hashicorp/vagrant/blob/master/plugins/communicators/winrm/communicator.rb#L145). As someone mentioned above in terms of keeping the experience consistent between the two products it'd probably be better to go with this approach.

In terms of actually getting the option hooked up with packer we could maybe add a new Elevated property here (https://github.com/hashicorp/packer/blob/master/communicator/winrm/config.go) that would be used by the communicator to choose whether or not to run as elevated. I assume this would probably have a higher scope of impact since the provisioners may need to be refactored to take into account the new property.

So I think where I fall on this is that I'd review a PR for it but I'm not going to prioritize working on it because it feels like most use cases for it can be worked around, even if it's a little annoying.

I'm doing a bit of issue cleanup right now, and reading over this thoroughly -- it looks like the original issue was solved by the elevated powershell provisioner. Because of that, I'm going to close this issue, but if someone wants to work on the idea of an elevated chef install or an elevated winrm communicator, I'm still happy to review PRs.

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings