Chef Interactive

As you probably are aware, Chef is a tool which is meant for automatic provisioning and configuring of systems. So if you have a particular problem falling outside of the regular use cases, both posts on the internet and support enquiries of any kind will probably result in one of two answers: “that is not possible” or “you are doing it wrong”.

But - what if you really need this for a rather exotic task or even as an transitory solution?

Use Cases

So far, we have seen two specific use cases: Modernization of a provisioning workflow and use of tools with purely interactive setups.

Think of a factory where IT systems are provisioned as part of a larger engineering solution. Certainly, these IT systems are only a part of this system and probably even one that’s deemed non-central to the solution. Especially if the provisioning\production workflow is outsourced to a third party, you have to follow some constraints until more sophisticated solutions can be deployed. Sure, aiming for a “Big Bang” release which will completely change and optimize everything is an option. But in an enterprise context - how often does that usually go well?

So, in our less-than-hypothetical case we will imagine such a workflow where people are currently used to start a newly installed Windows server and then follow tasks such as checking\configuring parts of the system. Most of this we can replace with a Chef based solution. But what if there are cases where servers arrive with the wrong number of physical drives? Or we have variations which only can be corrected in some manual fashion? Usually, an assembly line should not have this problems - if you followed the Lean movement, you probably know the Jidoka principle. But for now, let’s say this is something we cannot influence at the point of the project, so we have to work with that.

You probably know one or two of those already: Setup tools which for some reason do not support any non-interactive setup. No flags on the installer or even some mandatory pop-up asking for an activation key. One of the pragmatic solutions is to use tools like AutoIt or AutoHotkey to wait for window events, simulate keystrokes or mouse clicks.

Our core problem in this case is, that Chef is executed as a background process and in a headless session. Not even a simulated UI - that means our AutoIt won’t work either.

Step 1: Get Interactive

There are two approaches which we came up with: one works with a tool from the Sysinternals suite by Microsoft called “psexec” and another one which only uses Windows-internal means to do the job. Both solutions require an interactive Windows session though, so we have to tackle this one first.

Windows systems have the ability to automatically login a user on system boot. Obviously, that is a bad idea security-wise - but in our use case, we are in a one-off provisioning workflow, so that is a bearable thing if it gets removed afterwards. Right?

There currently is no resource in Chef to activate\deactivate Autologin. But that is no problem, as we can write a simple Custom Resource around a registry_key resource setting the appropriate values:

resource_name :autologin
 
property :user, String, name_property: true
property :password, String
 
default_action :enable
 
action :enable do
  registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' do
    values [
      { name: 'DefaultUserName', type: :string, data: new_resource.user },
      { name: 'DefaultPassword', type: :string, data: new_resource.password },
      { name: 'AutoAdminLogon', type: :dword, data: 1 },
    ]
    sensitive true
    action :create
    notifies :reboot_now, 'reboot[now]', :immediate
  end
 
  reboot 'now' do
    action :nothing
  end
end
 
action :disable do
  registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' do
    values [
      { name: 'DefaultUserName', type: :string, data: '' },
      { name: 'DefaultPassword', type: :string, data: '' },
      { name: 'AutoAdminLogon', type: :dword, data: 0 },
    ]
    action :create
  end
end

As you can see, we also have a conditional reboot resource in this and it really means that the system is going to restart at this point in time. Another thing: user and password are in the registry in clear text! No other way to achieve this as far as I am aware, though.

If you use this resource, always keep in mind that

  • you deactivate autologin after you are done (no reboot required there)
  • a rerun of this will always reboot the server and go through this unless you add a proper guard to it (for example check for a registry key, if the program in question is already installed)

Step 2: Schedule That Task

Now how to execute a resource after the server booted up? You might have already guessed from the title - we will use the Windows Task Scheduler now. One of the ways it can be configured will work for us. This needs:

  • set the task to “on logon”, so it will actually start
  • property “Start when a user is logged on” (we covered that with our autologin resource before)
  • user\password for the task

As we want this in the same session which we are logged in, we will just snatch the user\password values from our autologin registry key. So no duplication of access data and we even made use of the rather ugly cleartext storage. The Chef windows_task resource is making our setup pretty easy.

resource_name :execute_interactive
 
property :command, String, name_property: true
 
default_action :enable
 
action :enable do
  require 'securerandom'
 
  # Extract from autologin settings
  autologin = registry_get_values('HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon')
  user_auto = autologin.select { |data| data[:name] == 'DefaultUserName' }.first[:data]
  password_auto = autologin.select { |data| data[:name] == 'DefaultPassword' }.first[:data]
  taskname = format('execute-interactive-%s', SecureRandom.hex(4))
 
  windows_task 'Create task ' + taskname do
    action [:create, :run]
    task_name taskname
    command new_resource.command
 
    interactive_enabled true
    user user_auto
    password password_auto
    sensitive true
 
    run_level :highest
    frequency :once
    start_time Time.now.strftime('%H:%M')
  end
 
  powershell_script 'Wait for completion of ' + taskname do
    action :run
    code <<~PS1
      Do {
        Start-Sleep -Seconds 1
      } while ((Get-ScheduledTaskInfo -TaskName #{taskname}).LastTaskResult -ge 0x41301)
    PS1
  end
 
  windows_task 'Remove task ' + taskname do
    task_name taskname
    action :delete
  end
end

To be honest, this is the bare minimum and our implementation is a bit more sophisticated than that. But it works! Notice, that this will always result in converged resources due to adding/removing the task - so if you check for that inside your pipeline, keep that in mind.

If you are going into AutoIt\AutoHotkey land, I would advise you to write a custom resource which only accepts the script source, so you get more of the Infrastructure as Code vibe. For an advanced Chef engineer, that should pose not significant problem to write.

Alternative: PSExec

Another way that would work is using the Sysinternals Tools by Microsoft, which include a tool to execute tasks. That tool includes switches for interactive use and running under a system account. So if you for some reason dislike the task manager solution and are fine with somehow bundling an executable with your cookbook, you can certainly go down that path. It will still use the same autologin resource, but only the implementation for our execute_interactive resource will differ by using an execute resource instead of windows_task

Summary

Even though people (rightfully) want you not to run programs interactively with Chef, it is certainly possible. It should always be a matter of last resort or a transitional step towards a more elegant solution. In our factory example, you could either prevent variation in process step inputs or change the underlying principle, e.g. via using virtualized servers with automatic deployment of VMware ESX and all needed images.

Similar Posts You Might Enjoy

Target Mode with Serial Devices

Target Mode with Serial Devices Usually, you will work with SSH or WinRM to connect to remote nodes and configure them. Those standard protocols bring along all the perks of a modern network connection: Encryption, Authentication, File transfers, etc But what if you have a device without network connectivity? - by Thomas Heinen

Ruby Layers with Serverless

Ruby Layers with Serverless After showing how easy it is to write AWS Lambda functions in Ruby, we will work on a way to build Layers with external dependencies or shared data in this post. - by Thomas Heinen

Ruby Lambdas with Serverless

Ruby Lambdas with Serverless Support for Ruby was added on AWS Lambda end of November 2018, with support via the Serverless Framework landing on the same day. - by Thomas Heinen