Connect to Remote Machine via WinRM (PowerShell PSSession)

Unlike SSH, connecting to a remote host with WinRM requires a little bit of a setup.
Since this is required for chef test kitchen set up, and I end up re-Googling for this every time I set up a new machine, I decided it is worth a blog post.

So there are potentially two parts to this, dependent on whether WinRM is already set up on your machine:

Connect to remote machine

First set up your credentials. Open up PowerShell and run

 $cred = Get-Credential

This will prompt you for your credentials and save them in the $cred variable. Enter the credentials you intend to use to connect to the remote machine.
Now enter PSSession using your remote host IP and the credentials you have just set up:

 Enter-PSSession 192.168.XXX.XXX -Authentication "basic" -Credential=$cred

If you get the error:

The WinRM client cannot process the request…

Continue reading on how to set up WinRM on your machine:

Set up WinRm on your machine

Open CMD as administrator
Enable WinRM

winrm quickconfig /q

To check your current WinRM configuration

winrm get winrm/config

If you get the error:

The WinRM client cannot process the request. Unencrypted traffic is currently disabled in the client configuration.

Enable unencrypted traffic in WinRM:

winrm set winrm/config/client @{AllowUnencrypted="true"}

If you get the error:

The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting.

Add the remote computer you are connecting to to Trusted hosts:

winrm set winrm/config/client @{TrustedHosts="192.168.XXX.XXX"}

If you do this fairly often and are behind a secure firewall you can save some time by adding everyone to your trusted hosts:

winrm set winrm/config/client @{TrustedHosts="*"}

Happy Remoting!

No, wait, the mandatory meme:

remote session

The Simplest Example Ever: How To Create and Use LWRP in Chef

This example will walk you through creating the most basic Chef LWRP starting from scratch or, in other words, a truly light weight lightweight resource provider πŸ™‚

lightweight

I am working with a cookbook named my_cookbook, and I will define an LWRP named awesome_lwrp.
To do this, I will need two files, the Resource and the Provider. By default, the names of the Provider and the Resource files should match.
 

Create the Resource

Create a new folder resources in your cookbook directory
Add a file named awesome_lwrp.rb

Define the simplest resource ever:

actions :add, :delete
default_action :add

attribute :awesomeString, :kind_of => String, :required => true
attribute :awesomeInt, :kind_of => Integer, :required => false, :default => 3

Explanation:
awesome_lwrp supports two actions, add and delete. The default action is add.
awesome_lwrp accepts two arguments. The first one is a string, and it is required. The second one is an integer, it is optional and has a default value of 3.

 

Create the Provider

Create a new folder providers in your cookbook directory
Add a file named awesome_lwrp.rb

Defining a simple provider:

# Support β€œno-operation” mode
def whyrun_supported?
  true
end

# Should be considered a requirement for any
# lightweight resource authored against the 11.0+ versions of the chef-client
# Using this method ensures that the chef-client can notify parent lightweight resources
# after embedded resources have finished processing
use_inline_resources

# Implement action "add"
action :add do
  # Check condition
  # Use resource attribute
  if powershell_output_true?(@new_resource.awesomeInt)
    Chef::Log.debug "No need for changes!"
  else
    # Use converge_by for whyrun mode
    converge_by("Making awesome LWRP updates") do
      # Execute updates
      # Use resource attribute
      run_powershell_script(@new_resource.awesomeString)
      # Notify that a node was updated successfully (actually redundant here)
      @new_resource.updated_by_last_action(true)
      Chef::Log.debug "Awesome String was echoed"
    end
  end
end

action :delete do
  # Some code here
end

# Execute updates - this is a dummy method that simply echoes a string
def run_powershell_script(awesomeString)
  powershell_script 'Awesome Script' do
      code "echo " + awesomeString
      action :run
  end
end

# Check if updates need to be executed
def powershell_output_true?(awesomeInt)
  # A powershell command that returns false so that the update will get executed
  ps_command = "(1+1) -eq " + awesomeInt.to_s
  cmd_str = "powershell -Command " " + ps_command + " " "
  # Run powershell from cmd
  cmd = shell_out(cmd_str, { :returns => [0] })
  # Check powershell output
  if(cmd.stdout =~ /true/i)
     Chef::Log.debug "PowerShell output is true"
    return true
  else
    Chef::Log.debug "PowerShell output is false"
    return false
  end
end

What this code does:
For the sake of simplicity only the “add” action is implemented.
The “add” action checks if the updates are needed, and if the answer is yes it executes the updates and notifies the chef server that the node was updated. I am using dummy PowerShell methods simply to demonstrate the method flow.

Note that:

  • It is best practice to use use_inline_resources
  • The attributes supplied to the resource can be accessed using @new_resource.attributeName
  • It is good practice to call @new_resource.updated_by_last_action(true) if updates were executed, however it is redundant if you are using use_inline_resources and/or converge_by
  • whyrun is used in combination with converge_by statements to support a “no-operation” mode, in which chef-client is printing out what updates would be executed, without actually executing them

I am not going to go into depth on whyrun_supported and use_inline_resources. For details please check chef docs

 

Calling the new awesome LWRP

Now you can call the resource in your recipe as following:

my_cookbook_awesome_lwrp 'Awesome Resource Name' do
  action :add
  awesomeString node['my_cookbook']['awesome-string']
end

Note that:

  • The default LWRP name is the concatenation of the cookbook name with the resource/provider name using underscores.
  • Instantiated resource name (in this case ‘Awesome Resource Name’) needs to be provided to use the LWRP.
  • The required attributes are required πŸ˜‰
Appendix

In this example the LWRP is called with a node attribute. The node attribute can be defined in attributes/default.rb as following:

default['my_cookbook']['awesome-string'] = "Pumpkin Pie!"

 
* If your kitchen fails to recognize the method shell_out you need a newer version of Chef. Please specify this in your kitchen.yml

provisioner:
  name: chef_solo
  require_chef_omnibus: 12.4.1

 
That’s it! Happy lifting πŸ˜‰

Syntax Hell: Check PowerShell Command Output in Ruby (Chef)

I ran into this while creating an LWRP, and while the code below addresses a very straightforward need, the syntax is anything but straightforward, and is definitely worth documenting.

I am running a one line PowerShell command that outputs True or False, and I want my Ruby method return value to be the same as PowerShell command return value.
Reading the output from the powershell_script is not easy, so instead I am going to run my PowerShell command from the cmd and read the shell_out, and then compare the stdout of the command to “true” or “false”.
The code below is the most simplistic example:

def powershell_output_true?()
  ps_command = "(1+1) -eq 2"
  cmd_str = "powershell -Command " " + ps_command + " " "
  cmd = shell_out(cmd_str, { :returns => [0] })
  if(cmd.stdout =~ /true/i)
     Chef::Log.debug "PowerShell output is true"
    return true
  else
    Chef::Log.debug "PowerShell output is false"
    return false
  end
end

 
To compare stdout to a custom string:

def powershell_output_expected?()
  ps_command = "\"sum = {0}\" -f (1+2)"
  cmd_str = "powershell -Command " " + ps_command + " " "
  cmd = shell_out(cmd_str, { :returns => [0] })
  if(cmd.stdout =~ /sum = 3/i)
    Chef::Log.debug "PowerShell output is as expected"
    return true
  else
    Chef::Log.debug "PowerShell output is not as expected"
    return false
  end
end

The triple backslash here is to escape a quote inside of the PowerShell command. The output of the command is “sum = 3” .

Hope this saves you an hour or two of debugging from hell πŸ™‚
And one for the road:

syntax

How to pass an array Chef node attribute to powershell_script

This is a very short post on the magic of syntax in Chef + PowerShell .

My goal here is to pass an array of strings from Chef node attribute to powershell_script. The problem is that if I define a string array in ruby it fails with a syntax error in powershell. The answer on how to do it turned out to be fairly simple – if you define the node attribute to be a string using a combination of single and double quotes (see the example below) it is converted into an array in PowerShell code.

Attribute:

default['cookbook-name']['attribute-name'] = "'value1', 'value2', 'value3'"

And recipe:

powershell_script 'my script' do
  code <<-EOH
  $array = #{node['cookbook-name']['attribute-name']}
  ...
  EOH
  action :run
  guard_interpreter :powershell_script
  not_if "..."
end

 
That’s it! Happy cooking πŸ˜‰

Chef with PowerShell Scripts – Accessing Encrypted Data Bag Items

Chef data bags provide the ability to store unstructured data in JSON format. Data bags can be accessed from the chef server and can store global variables, or variables specific to an environment.
One of the great features of Chef data bags is the ability to encrypt information, such as passwords, to avoid storing sensitive data as plain text.
Data bag items can be encrypted using shared secret encryption, and then decrypted using the same secret file from a chef recipe. The items can also be decrypted with knife and used in a shell script. Here is how:

Creating passwords data bag:

mkdir data_bagspasswords
knife data bag create passwords

 
Create anmy_password.json file with the following information in passwords directory:

{
  "id": "the_password",
  "password": "[email protected]"
}

 
Create secret file using PowerShell:

$key = New-Object byte[](512)
$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key)
[Convert]::ToBase64String($key) | Out-File " C:secretsmy_secret" -encoding "UTF8"
[array]::Clear($key, 0, $key.Length)

 
Encrypt items in data bag
To encrypt a data bag item using the secret file:

knife data bag from file passwords my_password.json --secret-file C:secretsmy_secret

 
To verify the encryption:

knife data bag show passwords the_password

The output should look like this:
ps output
 
Decrypt items in data bag with knife:

knife data bag show passwords the_password--secret-file C:secretsmy_secret

 
To get the password from the data bag and decrypt it into a variable:

$json = knife data bag show passwords the_password --secret-file C:secretsmy_secret -F json
$password = (($json -join "`n") | ConvertFrom-Json).password

 
Now you have $password variable holding the value [email protected]
Happy cooking!