Skip to content

Commit

Permalink
Merge pull request #70 from chef/chris-rock/win-detection
Browse files Browse the repository at this point in the history
complete rewrite of windows version detection
  • Loading branch information
arlimus committed Feb 19, 2016
2 parents 9166573 + 8ad94dc commit ea935cc
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 73 deletions.
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ clone_folder: c:\projects\train
clone_depth: 1

cache:
- C:\Ruby21\bin\gem
- C:\Users\appveyor\.gem\ruby\2.1.0
- C:\Ruby21\lib\ruby\gems\2.1.0

install:
- systeminfo
Expand All @@ -33,7 +34,6 @@ install:
- ps: $env:PATH="C:\Ruby$env:ruby_version\bin;$env:PATH"
- ps: Write-Host $env:PATH
- gem install bundler --quiet --no-ri --no-rdoc
- gem update --system 2.4.5
- ruby --version
- gem --version
- bundler --version
Expand Down
1 change: 1 addition & 0 deletions lib/train/extras/command_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# author: Christoph Hartmann

require 'base64'
require 'winrm'
require 'train/errors'

module Train::Extras
Expand Down
101 changes: 37 additions & 64 deletions lib/train/extras/os_detect_windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,78 +7,51 @@
# OHAI https://github.com/chef/ohai
# by Adam Jacob, Chef Software Inc
#

require 'json'
require 'winrm'

module Train::Extras
module DetectWindows
# See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
# Product Type:
# Work Station (1)
# Domain Controller (2)
# Server (3)
WINDOWS_VERSIONS = {
'0' => '3.1',
'140' => '95',
'1410' => '98',
'1490' => 'ME',
'1351' => 'NT 3.51',
'3351' => 'NT 3.51 Server',
'1240' => 'NT 4.0',
'3240' => 'NT 4.0 Server',
'1250' => '2000',
'1251' => 'XP',
'3252' => 'Server 2003',
'1252' => 'Vista',
'3260' => 'Server 2008',
'1261' => '7',
'3261' => 'Server 2008 R2',
'1262' => '8',
'3262' => 'Server 2012',
'1263' => '8.1',
'3263' => 'Server 2012 R2',
'12100' => '10',
'32100' => 'Server 2016',
}.freeze

def windows_version(json)
producttype = json['OS']['ProductType'].to_s
# do not distigush between domain controller and server
producttype = '3' if producttype == '2'
platform = json['OSVersion']['Platform'].to_s
major = json['OSVersion']['Version']['Major'].to_s
minor = json['OSVersion']['Version']['Minor'].to_s
# construct it
producttype + platform + major + minor
end

def detect_windows
cmd = 'New-Object -Type PSObject | Add-Member -MemberType NoteProperty '\
'-Name OS -Value (Get-WmiObject -Class Win32_OperatingSystem) '\
'-PassThru | Add-Member -MemberType NoteProperty -Name OSVersion '\
'-Value ([Environment]::OSVersion) -PassThru | ConvertTo-Json'

# wrap the script to ensure we always run it via powershell
# especially in local mode, we cannot be sure that we get a Powershell
# we may just get a `cmd`. os detection and powershell command wrapper is
# not available when this code is executed
script = WinRM::PowershellScript.new(cmd)
cmd = "powershell -encodedCommand #{script.encoded}"

res = @backend.run_command(cmd)

# TODO: error as this shouldnt be happening at this point
res = @backend.run_command('cmd /c ver')
return false if res.exit_status != 0 or res.stdout.empty?

json = JSON.parse(res.stdout)
return false if json.nil? or json.empty?
version = windows_version(json)

# if the ver contains `Windows`, we know its a Windows system
version = res.stdout.strip
return false unless version.downcase =~ /windows/
@platform[:family] = 'windows'
@platform[:release] = WINDOWS_VERSIONS[version]

# try to extract release from eg. `Microsoft Windows [Version 6.3.9600]`
release = /\[(?<name>.*)\]/.match(version)
unless release[:name].nil?
# release is 6.3.9600 now
@platform[:release] = release[:name].downcase.gsub('version', '').strip
# fallback, if we are not able to extract the name from wmic later
@platform[:name] = "Windows #{@platform[:release]}"
end

# try to use wmic, but lets keep it optional
read_wmic

true
end

# reads os name and version from wmic
# @see https://msdn.microsoft.com/en-us/library/bb742610.aspx#EEAA
# Thanks to Matt Wrock (https://github.com/mwrock) for this hint
def read_wmic
res = @backend.run_command('wmic os get * /format:list')
if res.exit_status == 0
sys_info = {}
res.stdout.lines.each { |line|
m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
}

@platform[:release] = sys_info[:Version]
# additional info on windows
@platform[:build] = sys_info[:BuildNumber]
@platform[:name] = sys_info[:Caption]
@platform[:name] = @platform[:name].gsub('Microsoft', '').strip unless @platform[:name].empty?
@platform[:arch] = sys_info[:OSArchitecture]
end
end
end
end
1 change: 1 addition & 0 deletions test/unit/extras/os_detect_linux_test.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# encoding: utf-8
require 'helper'
require 'train/extras'

Expand Down
99 changes: 99 additions & 0 deletions test/unit/extras/os_detect_windows_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
require 'train/extras'

class OsDetectWindowsTester
attr_reader :platform, :backend
include Train::Extras::DetectWindows

def initialize
@platform = {}
@backend = Train::Transports::Mock.new.connection
@backend.mock_os({ family: 'windows' })
end
end

describe 'os_detect_windows' do
describe 'windows 2012' do
let(:detector) {
detector = OsDetectWindowsTester.new
detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.3.9600]\r\n", '', 0)
detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=9600\r\r\nCaption=Microsoft Windows Server 2012 R2 Standard\r\r\nOSArchitecture=64-bit\r\r\nVersion=6.3.9600\r\r\n" , '', 0)
detector
}

it 'sets the correct family/release for windows' do
detector.detect_windows
detector.platform[:family].must_equal('windows')
detector.platform[:name].must_equal('Windows Server 2012 R2 Standard')
detector.platform[:arch].must_equal('64-bit')
detector.platform[:release].must_equal('6.3.9600')
end
end

describe 'windows 2008' do
let(:detector) {
detector = OsDetectWindowsTester.new
detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.1.7601]\r\n", '', 0)
detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=7601\r\r\nCaption=Microsoft Windows Server 2008 R2 Standard \r\r\nOSArchitecture=64-bit\r\r\nVersion=6.1.7601\r\r\n" , '', 0)
detector
}

it 'sets the correct family/release for windows' do
detector.detect_windows
detector.platform[:family].must_equal('windows')
detector.platform[:name].must_equal('Windows Server 2008 R2 Standard')
detector.platform[:arch].must_equal('64-bit')
detector.platform[:release].must_equal('6.1.7601')
end
end

describe 'windows 7' do
let(:detector) {
detector = OsDetectWindowsTester.new
detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.1.7601]\r\n", '', 0)
detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=7601\r\r\nCaption=Microsoft Windows 7 Enterprise \r\r\nOSArchitecture=32-bit\r\r\nVersion=6.1.7601\r\r\n\r\r\n" , '', 0)
detector
}

it 'sets the correct family/release for windows' do
detector.detect_windows
detector.platform[:family].must_equal('windows')
detector.platform[:name].must_equal('Windows 7 Enterprise')
detector.platform[:arch].must_equal('32-bit')
detector.platform[:release].must_equal('6.1.7601')
end
end

describe 'windows 10' do
let(:detector) {
detector = OsDetectWindowsTester.new
detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 10.0.10240]\r\n", '', 0)
detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=10240\r\r\nCaption=Microsoft Windows 10 Pro\r\r\nOSArchitecture=64-bit\r\r\nVersion=10.0.10240\r\r\n\r\r\n" , '', 0)
detector
}

it 'sets the correct family/release for windows' do
detector.detect_windows
detector.platform[:family].must_equal('windows')
detector.platform[:name].must_equal('Windows 10 Pro')
detector.platform[:arch].must_equal('64-bit')
detector.platform[:release].must_equal('10.0.10240')
end
end

describe 'windows 98' do
let(:detector) {
detector = OsDetectWindowsTester.new
detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 4.10.1998]\r\n", '', 0)
detector.backend.mock_command('wmic os get * /format:list', nil , '', 1)
detector
}

it 'fallback to version number if wmic is not available' do
detector.detect_windows
detector.platform[:family].must_equal('windows')
detector.platform[:name].must_equal('Windows 4.10.1998')
detector.platform[:arch].must_equal(nil)
detector.platform[:release].must_equal('4.10.1998')
end
end
end
6 changes: 3 additions & 3 deletions test/windows/local_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

it 'verify os' do
os = conn.os
os[:name].must_equal nil
os[:name].must_equal 'Windows Server 2012 R2 Datacenter'
os[:family].must_equal "windows"
os[:release].must_equal "Server 2012 R2"
os[:arch].must_equal nil
os[:release].must_equal '6.3.9600'
os[:arch].must_equal '64-bit'
end

it 'run echo test' do
Expand Down
8 changes: 4 additions & 4 deletions test/windows/winrm_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

it 'verify os' do
os = conn.os
os[:name].must_equal nil
os[:family].must_equal "windows"
os[:release].must_equal "Server 2012 R2"
os[:arch].must_equal nil
os[:name].must_equal 'Windows Server 2012 R2 Datacenter'
os[:family].must_equal 'windows'
os[:release].must_equal '6.3.9600'
os[:arch].must_equal '64-bit'
end

it 'run echo test' do
Expand Down

0 comments on commit ea935cc

Please sign in to comment.