Chef ♥’s Windows

Here at Opscode we have the stated goal of creating “Infrastructure Automation for the Masses”. We won’t rest until Chef manages EVERYTHING! And everything includes a whole lot more than Linux servers – it also includes our brethren on the Windows platform. To that end, I wanted to give everyone a quick update on some killer new features we’ve added to Chef for managing Windows and mixed *nix/Windows environments. We’ll be covering:

  • Windows platform support in core Chef/Ohai Libraries
  • The knife-windows plugin for Chef’s CLI tool Knife which adds new subcommands for:
    • invoking remote commands via WinRM
    • bootstrapping Windows nodes via WinRM and SSH
  • The new PowerShell cookbook Opscode recently contributed to the community

Chef/Ohai Libraries

A majority of the resources that ship with Chef work out of the box on the Windows platform. These include: Service, Execute, User, Group, Mount, Gem Package, Remote File, Cookbook File, Ruby Block, Log. This primes the toolbox of the Windows cook with a powerful set of primitives to accomplish a majority of Windows-related configuration management tasks. If you have identified any missing resources be sure to open a feature request in the Chef ticketing system. The Chef 0.10 release also fixed a number of outstanding compatibility issues related running Chef on the Windows platform.

Likewise Ohai 0.6.4 currently has no issues profiling nodes on the Windows platform. It also fixed an annoying bug that required a modification to a nodes client.rb file.

knife-windows

My favorite new feature in the recent release of Chef 0.10 is the ability to extend Knife’s subcommand system with Knife Plugins. For details on how you create your own plugins please take a look at our prior blog post which outlined the creation of a simple plugin from start to finish. One of the knife plugins we shipped alongside Chef 0.10 is named knife-windows. This plugin adds additional functionality to Knife for configuring/interacting with nodes running Microsoft Windows.

Knife Plugins ship as Ruby gems so knife-windows is easily installable with the following one-liner:

$ gem install knife-windows
$ knife --help</p>

<p>[ ... SNIP ... ]</p>

<p>** BOOTSTRAP COMMANDS **
knife bootstrap FQDN (options)
knife bootstrap windows winrm FQDN (options)
knife bootstrap windows ssh FQDN (options)</p>

<p>[ ... SNIP ... ]</p>

<p>** WINRM COMMANDS **
knife winrm QUERY COMMAND (options)

Since knife-windows is written in pure Ruby (just like Chef) you should be able to install it on any platform that Chef supports. This means you can manage your Windows nodes from the comfort of bash on your favorite Linux distro or better yet zsh on OS X!

Let’s take a quick look at the new subcommands knife-windows brings to the party.

Running Remote Commands via WinRM – knife winrm

One of our community’s favorite core Knife subcommands is knife ssh. This subcommand gives you the ability to invoke a command via the SSH protocol across all (or a subset) of the nodes in your Chef-managed infrastructure. If your Windows nodes are already running an SSH daemon like freeSSHd or WinSSHD you can leverage knife ssh as is. While remote management via SSH is certainly possible on Windows servers, it’s not the protocol blessed by Microsoft. Currently, Microsoft recommends using Windows Remote Management (WinRM) which is their implementation of the of SOAP-based WS-Management Protocol. WinRM allows you to remotely call native objects in Windows. Basically WinRM allows you to open a remote session on a Windows server and then invoke commands against that server.

knife winrm exposes the same power as knife ssh except all commands are invoked via the WinRM protocol! So what can you do with knife winrm? Pretty much anything you would normally expect to do with knife ssh. The knife winrm even uses the same search syntax as the knife ssh and knife search subcommands.

Since code speaks louder than works let’s take a look at a few example commands.

Firstly you might find the uptime of all your web servers using this command:

$ knife winrm "role:web" "net statistics server" \
    -m -x Administrator -P 'secret_password'

command output:

ec2-50-xx-xx-124.compute-1.amazonaws.com Server Statistics for \
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Statistics since 5/18/2011 4:48:02 PM
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Sessions accepted                  1
ec2-50-xx-xx-124.compute-1.amazonaws.com Sessions timed-out                 0
ec2-50-xx-xx-124.compute-1.amazonaws.com Sessions errored-out               0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Kilobytes sent                     0
ec2-50-xx-xx-124.compute-1.amazonaws.com Kilobytes received                 0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Mean response time (msec)          0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com System errors                      0
ec2-50-xx-xx-124.compute-1.amazonaws.com Permission violations              0
ec2-50-xx-xx-124.compute-1.amazonaws.com Password violations                0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Files accessed                     0
ec2-50-xx-xx-124.compute-1.amazonaws.com Communication devices accessed     0
ec2-50-xx-xx-124.compute-1.amazonaws.com Print jobs spooled                 0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Times buffers exhausted
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com   Big buffers                      0
ec2-50-xx-xx-124.compute-1.amazonaws.com   Request buffers                  0
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com The command completed successfully.
ec2-50-xx-xx-124.compute-1.amazonaws.com

You could also invoke everyone’s favorite Windows command ipconfig against a single server (vs a search query in the prior example) using the following command:

$ knife winrm 'ec2-50-xx-xx-124.compute-1.amazonaws.com' 'ipconfig' \
    -m -x Administrator -P 'secret_password'

command output:

ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Windows IP Configuration
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Ethernet adapter Local Area Connection:
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com    Connection-specific DNS Suffix  . : ec2.internal
ec2-50-xx-xx-124.compute-1.amazonaws.com    Link-local IPv6 Address . . . . . : fe80::54f4:b9d4:16a4:b931%11
ec2-50-xx-xx-124.compute-1.amazonaws.com    IPv4 Address. . . . . . . . . . . : 10.108.14.167
ec2-50-xx-xx-124.compute-1.amazonaws.com    Subnet Mask . . . . . . . . . . . : 255.255.254.0
ec2-50-xx-xx-124.compute-1.amazonaws.com    Default Gateway . . . . . . . . . : 10.108.14.1
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Tunnel adapter isatap.ec2.internal:
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com    Media State . . . . . . . . . . . : Media disconnected
ec2-50-xx-xx-124.compute-1.amazonaws.com    Connection-specific DNS Suffix  . : ec2.internal
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Tunnel adapter Local Area Connection* 9:
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com    Connection-specific DNS Suffix  . : 
ec2-50-xx-xx-124.compute-1.amazonaws.com    IPv6 Address. . . . . . . . . . . : 2001:0:4137:9e76:41d:1efb:f593:f158
ec2-50-xx-xx-124.compute-1.amazonaws.com    Link-local IPv6 Address . . . . . : fe80::41d:1efb:f593:f158%12
ec2-50-xx-xx-124.compute-1.amazonaws.com    Default Gateway . . . . . . . . . : ::</p>

<p>

Or better yet force a Chef run:

$ knife winrm ec2-50-xx-xx-124.compute-1.amazonaws.com \
    'chef-client -c c:/chef/client.rb' -m -x Administrator -P 'secret_password'

command output:

ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:04 +0100] INFO: *** Chef 0.10.0 ***
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:17 +0100] INFO: Run List is [recipe1]
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:17 +0100] INFO: Run List expands to 1
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:17 +0100] INFO: Starting Chef Run for ip-0A6C0EA7.ec2.internal
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:20 +0100] INFO: Loading cookbooks 1
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:21 +0100] INFO: PowerShell 2.0 is already enabled on this version of Windows: 6.1.7601</p>

<p>[ ... SNIP ... ]</p>

<p>ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:26 +0100] INFO: Chef Run complete in 8.439546 seconds
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:26 +0100] INFO: Running report handlers
ec2-50-xx-xx-124.compute-1.amazonaws.com [Fri, 20 May 2011 22:33:26 +0100] INFO: Report handlers complete

It even includes knife ssh ‘s slick interactive mode:

$ knife winrm ec2-50-xx-xx-124.compute-1.amazonaws.com interactive \
    -m -x Administrator -P "secret_password"

command output:

Connected to ec2-50-xx-xx-124.compute-1.amazonaws.com</p>

<p>To run a command on a list of servers, do:
  on SERVER1 SERVER2 SERVER3; COMMAND
  Example: on latte foamy; echo foobar</p>

<p>To exit interactive mode, use 'quit!'</p>

<p>knife-winrm&gt; dir
ec2-50-xx-xx-124.compute-1.amazonaws.com  Volume in drive C has no label.
ec2-50-xx-xx-124.compute-1.amazonaws.com  Volume Serial Number is FC27-1871
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com  Directory of C:\Users\Administrator
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:35 PM    &lt;DIR&gt;          .
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:35 PM    &lt;DIR&gt;          ..
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:35 PM    &lt;DIR&gt;          .gem
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/21/2011  03:27 AM               519 config.yml
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Contacts
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Desktop
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Documents
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Downloads
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Favorites
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Links
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Music
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Pictures
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Saved Games
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Searches
ec2-50-xx-xx-124.compute-1.amazonaws.com 05/18/2011  06:30 PM    &lt;DIR&gt;          Videos
ec2-50-xx-xx-124.compute-1.amazonaws.com                1 File(s)            519 bytes
ec2-50-xx-xx-124.compute-1.amazonaws.com               14 Dir(s)  19,469,873,152 bytes free
knife-winrm&gt;

Your Window’s nodes will need to have properly configured WinRM installation before you can start invoking remote commands against them with the knife-windows. Please check out knife-window’s README and the WinRM configuration guide for more information and examples.

Bootstrapping Windows Nodes – knife bootstrap windows *

The goal of a Chef bootstrap is to get Chef installed on the target system so it can run chef-client and register with a Chef Server (like the Opscode Platform). The main assumption is nothing more than a baseline OS installation exists. knife-windows ships with 2 new subcommands for bootstrapping Windows nodes via the WinRM and SSH protocols. Although the remote command protocols differ, both subcommands leverage the same bootstrap template and thus perform the same steps on the target node:

  • Installs Ruby 1.8.7 via the Ruby Installer for Windows which also includes the latest version of RubyGems
  • Installs the RubyInstaller Development Kit (DevKit). The RubyInstaller Development Kit (DevKit) is a MSYS/MinGW based toolkit than enables you to build many of the native C/C++ extensions available for Ruby.
  • Installs required Windows-related gems from RubyGems.org
  • Installs latest Chef version from RubyGems.org including Ohai and Chef. The Chef version is configurable using the —bootstrap-version option.
  • Writes the validation.pem per the local knife configuration.
  • Writes a default config file for Chef (C:\chef\client.rb) using values per the local knife configuration.
  • Creates a file containing the run list given on the command line.
  • Runs chef-client with that run list.

Here is a command that might be used to bootstrap a production webserver via WinRM:

$ knife bootstrap windows winrm ec2-50-xx-xx-124.compute-1.amazonaws.com \
    -r 'role[webserver]' -E production \
    -x Administrator -P 'super<em>secret</em>password'

Output from this command would look something like this:

Bootstrapping Chef on ec2-50-xx-xx-124.compute-1.amazonaws.com
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 1&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 2&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 3&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 4&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 5&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com &quot;Rendering bootstrap.bat chunk 6&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;mkdir C:\chef 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;(
ec2-50-xx-xx-124.compute-1.amazonaws.com echo.url = WScript.Arguments.Named(&quot;url&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.path = WScript.Arguments.Named(&quot;path&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objXMLHTTP = CreateObject(&quot;MSXML2.ServerXMLHTTP.6.0&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set wshShell = CreateObject( &quot;WScript.Shell&quot; )<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objUserVariables = wshShell.Environment(&quot;USER&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.'http proxy is optional<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.'attempt to read from HTTP<em>PROXY env var first<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.On Error Resume Next<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.If NOT (objUserVariables(&quot;HTTP</em>PROXY&quot;) = &quot;&quot;) Then<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objXMLHTTP.setProxy 2, objUserVariables(&quot;HTTP<em>PROXY&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.'fall back to named arg<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.ElseIf NOT (WScript.Arguments.Named(&quot;proxy&quot;) = &quot;&quot;) Then<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objXMLHTTP.setProxy 2, WScript.Arguments.Named(&quot;proxy&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.End If<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.On Error Goto 0<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objXMLHTTP.open &quot;GET&quot;, url, false<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objXMLHTTP.send() 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.If objXMLHTTP.Status = 200 Then<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objADOStream = CreateObject(&quot;ADODB.Stream&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.Open 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.Type = 1<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.Write objXMLHTTP.ResponseBody<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.Position = 0<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objFSO = Createobject(&quot;Scripting.FileSystemObject&quot;)<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.If objFSO.Fileexists(path) Then objFSO.DeleteFile path<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objFSO = Nothing<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.SaveToFile path<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.objADOStream.Close 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objADOStream = Nothing<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.End if<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.Set objXMLHTTP = Nothing 
ec2-50-xx-xx-124.compute-1.amazonaws.com ) 1&gt;C:\chef\wget.vbs 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cscript /nologo C:\chef\wget.vbs /url:http://files.rubyforge.vm.bytemark.co.uk/rubyinstaller/rubyinstaller-1.8.7-p334.exe /path:C:\Users\ADMINI~1\AppData\Local\Temp\rubyinstaller.exe 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;C:\Users\ADMINI~1\AppData\Local\Temp\rubyinstaller.exe /verysilent /dir=&quot;C:\ruby&quot; /tasks=&quot;assocfiles,modpath&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cscript /nologo C:\chef\wget.vbs /url:http://cloud.github.com/downloads/oneclick/rubyinstaller/DevKit-tdm-32-4.5.1-20101214-1400-sfx.exe /path:C:\Users\ADMINI~1\AppData\Local\Temp\rubydevkit.exe 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;mkdir C:\DevKit 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;copy C:\Users\ADMINI~1\AppData\Local\Temp\rubydevkit.exe C:\DevKit 
ec2-50-xx-xx-124.compute-1.amazonaws.com         1 file(s) copied.
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\DevKit\rubydevkit.exe -y 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\ruby\bin\ruby c:/DevKit/dk.rb init 
ec2-50-xx-xx-124.compute-1.amazonaws.com [INFO] found RubyInstaller v1.8.7 at C:/ruby
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com Initialization complete! Please review and modify the auto-generated
ec2-50-xx-xx-124.compute-1.amazonaws.com 'config.yml' file to ensure it contains the root directories to all
ec2-50-xx-xx-124.compute-1.amazonaws.com of the installed Rubies you want enhanced by the DevKit.
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\ruby\bin\ruby c:/DevKit/dk.rb install 
ec2-50-xx-xx-124.compute-1.amazonaws.com [INFO] Updating convenience notice gem override for 'C:/ruby'
ec2-50-xx-xx-124.compute-1.amazonaws.com [INFO] Installing 'C:/ruby/lib/ruby/site</em>ruby/devkit.rb'
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;SET PATH=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\DevKit\mingw\libexec\gcc\mingw32\4.5.1;C:\DevKit\mingw\libexec\gcc\mingw32\4.5.1 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;SETX PATH &quot;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\DevKit\mingw\libexec\gcc\mingw32\4.5.1;C:\DevKit\mingw\libexec\gcc\mingw32\4.5.1&quot; 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com SUCCESS: Specified value was saved.
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\ruby\bin\gem install win32-open3 rdp-ruby-wmi windows-api windows-pr --no-rdoc --no-ri --verbose 
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem win32-open3-0.3.2-x86-mingw32
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed win32-open3-0.3.2-x86-mingw32
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem rdp-ruby-wmi-0.3.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed rdp-ruby-wmi-0.3.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem win32-api-1.4.8-x86-mingw32
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem windows-api-0.4.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed win32-api-1.4.8-x86-mingw32
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed windows-api-0.4.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem windows-pr-1.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed windows-pr-1.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com 5 gems installed
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\ruby\bin\gem install ohai --no-rdoc --no-ri --verbose 
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem yajl-ruby-0.8.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Temporarily enhancing PATH to include DevKit...
ec2-50-xx-xx-124.compute-1.amazonaws.com Building native extensions.  This could take a while...
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem systemu-2.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem mixlib-cli-1.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem mixlib-config-1.1.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem mixlib-log-1.3.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem ohai-0.6.4
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed yajl-ruby-0.8.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed systemu-2.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed mixlib-cli-1.2.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed mixlib-config-1.1.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed mixlib-log-1.3.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed ohai-0.6.4
ec2-50-xx-xx-124.compute-1.amazonaws.com 6 gems installed
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;cmd.exe /C C:\ruby\bin\gem install chef --no-rdoc --no-ri --verbose --version 0.10.0 
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem mixlib-authentication-1.1.4
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem mime-types-1.16
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem rest-client-1.6.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Installin
ec2-50-xx-xx-124.compute-1.amazonaws.com g gem bunny-0.6.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem json-1.5.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Building native extensions.  This could take a while...
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem polyglot-0.3.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem treetop-1.4.9
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem net-ssh-2.1.4
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem net-ssh-gateway-1.1.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem net-ssh-multi-1.0.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem erubis-2.7.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem moneta-0.6.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem highline-1.6.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem uuidtools-2.1.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Installing gem chef-0.10.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed mixlib-authentication-1.1.4
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed mime-types-1.16
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed rest-client-1.6.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed bunny-0.6.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed json-1.5.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed polyglot-0.3.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed treetop-1.4.9
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed net-ssh-2.1.4
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed net-ssh-gateway-1.1.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed net-ssh-multi-1.0.1
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed erubis-2.7.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed moneta-0.6.0
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed highline-1.6.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed uuidtools-2.1.2
ec2-50-xx-xx-124.compute-1.amazonaws.com Successfully installed chef-0.10.0
ec2-50-xx-xx-124.compute-1.amazonaws.com 15 gems installed
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;(
ec2-50-xx-xx-124.compute-1.amazonaws.com echo.-----BEGIN RSA PRIVATE KEY-----<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.79QkDTFG43/J9d/AEtlOJazvomkUedEw5t76EUWlZSNwh/ij6y+NHSo=<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.-----END RSA PRIVATE KEY----- 
ec2-50-xx-xx-124.compute-1.amazonaws.com ) 1&gt;C:\chef\validation.pem 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;(
ec2-50-xx-xx-124.compute-1.amazonaws.com echo.require 'win32ole'<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.WIN32OLE.codepage = WIN32OLE::CP<em>UTF8<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.log</em>level        :info<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.log<em>location     STDOUT<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.chef</em>server<em>url  &quot;https://api.opscode.com/organizations/schisamo-dev&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.validation</em>client<em>name &quot;schisamo-dev-validator&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.client</em>key        &quot;c:/chef/client.pem&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.validation<em>key    &quot;c:/chef/validation.pem&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.file</em>cache<em>path   &quot;c:/chef/cache&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.file</em>backup<em>path  &quot;c:/chef/backup&quot;<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.cache</em>options     ({:path =&gt; &quot;c:/chef/cache/checksums&quot;, :skip<em>expires =&gt; true})<br />
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo. 
ec2-50-xx-xx-124.compute-1.amazonaws.com  echo.# Using default node name (fqdn) 
ec2-50-xx-xx-124.compute-1.amazonaws.com ) 1&gt;C:\chef\client.rb 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;(echo.{&quot;run</em>list&quot;:[]}) 1&gt;C:\chef\first-boot.json 
ec2-50-xx-xx-124.compute-1.amazonaws.com 
ec2-50-xx-xx-124.compute-1.amazonaws.com C:\Users\Administrator&gt;c:/ruby/bin/ruby c:/ruby/bin/chef-client -c c:/chef/client.rb -j c:/chef/first-boot.json -E <em>default 
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:23 +0100] INFO: *** Chef 0.10.0 ***
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:29 +0100] INFO: Client key c:/chef/client.pem is not present - registering
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:44 +0100] INFO: HTTP Request Returned 404 Not Found: Cannot load node ip-0A6C0EA7.ec2.internal
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:47 +0100] INFO: Setting the run</em>list to [] from JSON
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:47 +0100] INFO: Run List is []
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:47 +0100] INFO: Run List expands to []
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:47 +0100] INFO: Starting Chef Run for ip-0A6C0EA7.ec2.internal
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:48 +0100] INFO: Loading cookbooks []
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:48 +0100] WARN: Node ip-0A6C0EA7.ec2.internal has an empty run list.
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:52 +0100] INFO: Chef Run complete in 4.69557 seconds
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:52 +0100] INFO: Running report handlers
ec2-50-xx-xx-124.compute-1.amazonaws.com [Sat, 21 May 2011 03:28:52 +0100] INFO: Report handlers complete

Here’s the same command done via SSH:

$ knife bootstrap windows ssh ec2-50-xx-xx-124.compute-1.amazonaws.com \
    -r 'role[webserver]' -x Administrator -i ~/.ssh/id_rsa -E production

PowerShell Cookbook

If you talk to any sysadmin managing Windows servers at scale and the first tool he’ll mention to you is PowerShell. PowerShell is the gateway to doing most everything cool on modern Windows servers. We recently shipped a cookbook that makes installing, activating and configuring PowerShell 2.0 on most modern versions of Windows very easy. The cookbook also ships with a Chef resource/provider for executing scripts using the PowerShell interpreter. It works exactly like the existing bash provider that is used heavily for automation tasks on Unix platforms.

Here’s a few quick usage examples:

</p>

<h1>write out to an interpolated path</h1>

<p>powershell &quot;write-to-interpolated-path&quot; do
  code &lt;&lt;-EOH
  $stream = [System.IO.StreamWriter] &quot;#{Chef::Config[:file<em>cache</em>path]}/powershell-test.txt&quot;
  $stream.WriteLine(&quot;In #{Chef::Config[:file<em>cache</em>path]}...word.&quot;)
  $stream.close()
  EOH
end</p>

<h1>use the change working directory attribute</h1>

<p>powershell &quot;cwd-then-write&quot; do
  cwd Chef::Config[:file<em>cache</em>path]
  code &lt;&lt;-EOH
  $stream = [System.IO.StreamWriter] &quot;C:/powershell-test2.txt&quot;
  $pwd = pwd
  $stream.WriteLine(&quot;This is the contents of: $pwd&quot;)
  $dirs = dir
  foreach ($dir in $dirs) {
    $stream.WriteLine($dir.fullname)
  }
  $stream.close()
  EOH
end</p>

<h1>pass an env var to script</h1>

<p>powershell &quot;read-env-var&quot; do
  cwd Chef::Config[:file<em>cache</em>path]
  environment ({'foo' =&gt; 'BAZ'})
  code &lt;&lt;-EOH
  $stream = [System.IO.StreamWriter] &quot;./test-read-env-var.txt&quot;
  $stream.WriteLine(&quot;FOO is $foo&quot;)
  $stream.close()
  EOH
end

Obviously these are pretty contrived examples, but they illustrate the power that is unlocked by the powershell script provider.

The Future

Go forth and automate some Windows servers! We here at Opscode are very excited about the new chapter that is unfolding as the Windows and Chef communities cross paths. We’ll be releasing even more cookbooks targeting the Windows platform and we’d love the community to begin writing/sharing some cookbooks that cover common Windows automation tasks (clustered SQL Server installations anyone). As always, feel free to ask for help on our mailing list and on our IRC channel (irc.freenode.net#chef).

Archives