Class: VagrantPlugins::WindowsDomain::Provisioner

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant-windows-domain/provisioner.rb

Overview

Windows Domain Provisioner Plugin.

Connects and Removes a guest Machine from a Windows Domain.

Constant Summary collapse

WINDOWS_DOMAIN_GUEST_RUNNER_PATH =

Default path for storing the transient script runner

"c:/tmp/vagrant-windows-domain-runner.ps1"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(machine, config) ⇒ Provisioner

Constructs the Provisioner Plugin.

Parameters:

  • machine (Machine)

    The guest machine that is to be provisioned.

  • config (Config)

    The Configuration object used by the Provisioner.



36
37
38
39
40
41
# File 'lib/vagrant-windows-domain/provisioner.rb', line 36

def initialize(machine, config)
  super

  @logger = Log4r::Logger.new("vagrant::provisioners::vagrant_windows_domain")
  @restart_sleep_duration = 10
end

Instance Attribute Details

#old_computer_nameObject

The current Computer Name.

Used to determine whether or not we need to rename the computer on join. This parameter should not be manually set.



29
30
31
# File 'lib/vagrant-windows-domain/provisioner.rb', line 29

def old_computer_name
  @old_computer_name
end

#restart_sleep_durationObject

Returns the value of attribute restart_sleep_duration.



23
24
25
# File 'lib/vagrant-windows-domain/provisioner.rb', line 23

def restart_sleep_duration
  @restart_sleep_duration
end

Instance Method Details

#configure(root_config) ⇒ Object

Configures the Provisioner.

Parameters:

  • root_config (Config)

    The default configuration from the Vagrant hierarchy.

Raises:



46
47
48
# File 'lib/vagrant-windows-domain/provisioner.rb', line 46

def configure(root_config)
  raise WindowsDomainError, :unsupported_platform if !windows?
end

#destroyObject

Cleanup after a destroy action.

This is the method called when destroying a machine that allows for any state related to the machine created by the provisioner to be cleaned up.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/vagrant-windows-domain/provisioner.rb', line 153

def destroy
  if @config && @config.domain != nil
    if is_joined_to_domain()
      set_credentials
      result = leave_domain
      if result
        @logger.debug("Need to reboot to leave the domain correctly")
        restart_guest
      end
    end
  else
    @logger.debug("Not leaving domain on `destroy` action - no valid configuration detected")
    return
  end
end

#generate_command_arguments(add_to_domain = true) ⇒ Object

Generates the argument list



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/vagrant-windows-domain/provisioner.rb', line 238

def generate_command_arguments(add_to_domain=true)

  if add_to_domain
    params = {"-DomainName" => @config.domain }

    if @config.unsecure
      params["-Unsecure"] = nil
    else
      params["-Credential $credentials"] = nil
    end

    if @config.computer_name != nil && @config.computer_name != @old_computer_name
      params["-NewName"] = "'#{@config.computer_name}'"
    end

    if @config.ou_path
      params["-OUPath"] = "'#{@config.ou_path}'"
    end

    # Remove with unsecure
    join_params = @config.join_options.map { |a| "#{a}" }.join(',')
    if join_params.to_s != ''
      params["-Options"] = join_params
    end
  else
    params = {}
    if !@config.unsecure
      params["-UnjoinDomainCredential $credentials"] = nil
    end
  end

  params.map { |k,v| "#{k}" + (!v.nil? ? " #{v}": '') }.join(' ')
end

#generate_command_arguments_renameObject

Generates the argument list for rename



228
229
230
231
232
233
234
235
# File 'lib/vagrant-windows-domain/provisioner.rb', line 228

def generate_command_arguments_rename

  params = {"-NewName" => @config.computer_name}
  params["-DomainCredential $credentials"] = nil
  join_params = @config.join_options.map { |a| "#{a}" }.join(',')
  params.map { |k,v| "#{k}" + (!v.nil? ? " #{v}": '') }.join(' ') + join_params
  
end

#generate_command_runner_script(add_to_domain = true) ⇒ String

Generates a PowerShell runner script from an ERB template

Parameters:

  • add_to_domain (boolean) (defaults to: true)

    Whether or not to add or remove the computer to the domain (default: true).

Returns:

  • (String)

    The interpolated PowerShell script.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/vagrant-windows-domain/provisioner.rb', line 204

def generate_command_runner_script(add_to_domain=true)
  path = File.expand_path("../templates/runner.ps1", __FILE__)

  if @config.computer_name != nil && @old_computer_name != nil && @config.computer_name.casecmp(@old_computer_name) != 0
    @config.rename = true
  end
  
  Vagrant::Util::TemplateRenderer.render(path, options: {
      config: @config,
      username: @config.username,
      password: @config.password,
      domain: @config.domain,
      computer_name: @config.computer_name,
      ou_path: @config.ou_path,
      add_to_domain: add_to_domain,
      unsecure: @config.unsecure,
      rename: @config.rename,
      parameters: generate_command_arguments(add_to_domain)
  }, options_rename: {
      parameters: generate_command_arguments_rename
  })
end

#get_guest_computer_name(machine) ⇒ Object

Gets the Computer Name from the guest machine



333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/vagrant-windows-domain/provisioner.rb', line 333

def get_guest_computer_name(machine)
  computerName = ""
  machine.communicate.shell.powershell("$env:COMPUTERNAME") do |type, data|
    if !data.chomp.empty?
      if [:stderr, :stdout].include?(type)
        computerName = data.chomp
        @logger.info("Detected guest computer name: #{computerName}")
      end
    end
  end

  computerName
end

#is_joined_to_domainObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/vagrant-windows-domain/provisioner.rb', line 68

def is_joined_to_domain()
  command = "    function Test-JoinedToADomain(){\n      $computerSystem = gwmi win32_computersystem\n      $partOfDomain = $computerSystem.PartOfDomain\n      if ($partofDomain) {\n        exit 0\n      } else {\n        exit 1\n      }\n    }\n    Test-JoinedToADomain\n  EOH\n  @machine.communicate.test(command, sudo: true)\nend\n"

#is_part_of_domain(computer_name, domain) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/vagrant-windows-domain/provisioner.rb', line 51

def is_part_of_domain(computer_name, domain)
  command = "    function Test-PartOfDomain($computerName, $domain){\n      $computerSystem = gwmi win32_computersystem\n      $partofDomain = ($computerSystem.Name -eq $computerName) -and ($computerSystem.PartOfDomain) -and ($computerSystem.Domain -eq $domain) \n\n      if ($partofDomain) {\n        exit 0\n      } else {\n        exit 1\n      }\n    }\n    Test-PartOfDomain -computerName '\#{computer_name}' -domain '\#{domain}'\n  EOH\n  @machine.communicate.test(command, sudo: true)\nend\n"

#join_domainObject

Join the guest machine to a Windows Domain.

Generates, writes and runs a script to join a domain.



118
119
120
# File 'lib/vagrant-windows-domain/provisioner.rb', line 118

def join_domain        
  run_remote_command_runner(write_command_runner_script(generate_command_runner_script(true)))
end

#leave_domainObject Also known as: unjoin_domain

Removes the guest machine from a Windows Domain.

Generates, writes and runs a script to leave a domain.



125
126
127
# File 'lib/vagrant-windows-domain/provisioner.rb', line 125

def leave_domain
  run_remote_command_runner(write_command_runner_script(generate_command_runner_script(false)))
end

#provisionObject

Run the Provisioner!



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/vagrant-windows-domain/provisioner.rb', line 86

def provision
  verify_guest_capability

  @old_computer_name = get_guest_computer_name(machine)

  result = is_part_of_domain(config.computer_name, config.domain)

  if result
    @machine.env.ui.say(:info, "Guest machine with computer name '#{config.computer_name}' is already a member of domain '#{config.domain}'")
  else
    @machine.env.ui.say(:info, "Connecting guest machine to domain '#{config.domain}' with computer name '#{config.computer_name}'")

    set_credentials

    result = join_domain

    remove_command_runner_script

    if result
      #Often requires 2 reboots to ensure that all AD stuff has been applied
      @logger.debug("Need to reboot to join the domain correctly - 1st reboot")
      restart_guest

      @logger.debug("Need to reboot to join the domain correctly - 2nd reboot")
      restart_guest
    end
  end
end

#remove_command_runner_scriptObject

Remove temporary run script as it may contain sensitive plain-text credentials.



293
294
295
# File 'lib/vagrant-windows-domain/provisioner.rb', line 293

def remove_command_runner_script
  @machine.communicate.sudo("del #{WINDOWS_DOMAIN_GUEST_RUNNER_PATH}")
end

#restart_guestObject

Restarts the Computer and waits



170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/vagrant-windows-domain/provisioner.rb', line 170

def restart_guest
  @machine.env.ui.say(:info, "Restarting computer for updates to take effect.")
  options = {}
  options[:provision_ignore_sentinel] = false
  options[:lock] = false
  @machine.action(:reload, options)
  Timeout.timeout(@machine.config.vm.boot_timeout) do
    begin
      sleep @restart_sleep_duration
    end until @machine.communicate.ready?
  end
end

#run_remote_command_runner(script_path) ⇒ boolean

Runs the PowerShell script on the guest machine.

Streams the output of the command to the UI

Returns:

  • (boolean)

    The result of the remote command



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/vagrant-windows-domain/provisioner.rb', line 301

def run_remote_command_runner(script_path)
  @machine.ui.info(I18n.t(
    "vagrant_windows_domain.running"))

  opts = {
    elevated:    true,
    error_check: true,
    error_key:   nil, # use the error_class message key
    good_exit:   0,
    shell:       :powershell
  }

  # A bit of an ugly dance, but this is how we get neat, colourised output and exit codes from a Powershell run
  last_type = nil
  new_line = ""
  error = false
  machine.communicate.sudo("powershell -ExecutionPolicy Bypass -OutputFormat Text -file #{script_path}", opts) do |type, data|
    if !data.chomp.empty?
      error = true if type == :stderr
      if [:stderr, :stdout].include?(type)
        color = type == :stdout ? :green : :red
        new_line = "\r\n" if last_type != nil and last_type != type
        last_type = type
        @machine.ui.info( new_line + data.chomp, color: color, new_line: false, prefix: false)
      end
    end
  end

  error == false
end

#set_credentialsObject

Ensure credentials are provided.

Get username/password from user if not provided as part of the config.



134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/vagrant-windows-domain/provisioner.rb', line 134

def set_credentials
  if (config.username == nil)
    @logger.info("==> Requesting username as none provided")
    config.username = @machine.env.ui.ask("Please enter your domain username: ")
  else
    @logger.info("==> Using domain username: #{config.username}")
  end

  if (config.password == nil)
    @logger.info("==> Requesting password as none provided")
    config.password = @machine.env.ui.ask("Please enter your domain password (output will be hidden): ", {:echo => false})
  end
end

#verify_binary(binary) ⇒ Object

Verify a binarycommand is executable on the guest machine.



191
192
193
194
195
196
197
198
# File 'lib/vagrant-windows-domain/provisioner.rb', line 191

def verify_binary(binary)
  @machine.communicate.sudo(
    "which #{binary}",
    error_class: WindowsDomainError,
    error_key: :binary_not_detected,
    domain: config.domain,
    binary: binary)
end

#verify_guest_capabilityObject

Verify that we can call the remote operations. Required to add the computer to a Domain.



185
186
187
188
# File 'lib/vagrant-windows-domain/provisioner.rb', line 185

def verify_guest_capability
  verify_binary("Add-Computer")
  verify_binary("Remove-Computer")
end

#windows?Boolean

Is the guest Windows?

Returns:

  • (Boolean)


348
349
350
351
# File 'lib/vagrant-windows-domain/provisioner.rb', line 348

def windows?
  # If using WinRM, we can assume we are on Windows
  @machine.config.vm.communicator == :winrm
end

#write_command_runner_script(script) ⇒ String

Writes the PowerShell runner script to a location on the guest.

Parameters:

  • script (String)

    The PowerShell runner script.

Returns:

  • (String)

    the Path to the uploaded location on the guest machine.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/vagrant-windows-domain/provisioner.rb', line 276

def write_command_runner_script(script)
  guest_script_path = WINDOWS_DOMAIN_GUEST_RUNNER_PATH
  file = Tempfile.new(["vagrant-windows-domain-runner", "ps1"])
  begin
    file.write(script)
    file.fsync
    file.close
    @machine.communicate.upload(file.path, guest_script_path)
  ensure
    file.close
    file.unlink
  end
  guest_script_path
end