diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index e21399d2b..f515bed9e 100644 --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -17,6 +17,10 @@ module VagrantPlugins class WinRMShell include Vagrant::Util::Retryable + # Exit code generated when user is invalid. Can occur + # after a hostname update + INVALID_USERID_EXITCODE = -196608 + # These are the exceptions that we retry because they represent # errors that are generally fixed from a retry and don't # necessarily represent immediate failure cases. @@ -71,13 +75,22 @@ module VagrantPlugins def elevated(command, opts = {}, &block) connection.shell(:elevated) do |shell| shell.interactive_logon = opts[:interactive] || false - uname = shell.username - begin - shell.username = elevated_username - execute_with_rescue(shell, command, &block) - ensure - shell.username = uname + result = execute_with_rescue(shell, command, &block) + if result.exitcode == INVALID_USERID_EXITCODE && result.stderr.include?(":UserId:") + uname = shell.username + ename = elevated_username + if uname != ename + @logger.warn("elevated command failed due to username error") + @logger.warn("retrying command using machine prefixed username - #{ename}") + begin + shell.username = ename + result = execute_with_rescue(shell, command, &block) + ensure + shell.username = uname + end + end end + result end end @@ -224,11 +237,8 @@ module VagrantPlugins end def elevated_username - if @elevated_username - return @elevated_username - end if username.include?("\\") - return @elevated_username = username + return username end computername = "" powershell("Write-Output $env:computername") do |type, data| @@ -236,9 +246,9 @@ module VagrantPlugins end computername.strip! if computername.empty? - return @elevated_username = username + return username end - @elevated_username = "#{computername}\\#{username}" + "#{computername}\\#{username}" end end #WinShell class end diff --git a/test/unit/plugins/communicators/winrm/shell_test.rb b/test/unit/plugins/communicators/winrm/shell_test.rb index 9a32a5df8..0c1a57ca6 100644 --- a/test/unit/plugins/communicators/winrm/shell_test.rb +++ b/test/unit/plugins/communicators/winrm/shell_test.rb @@ -75,10 +75,16 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do end describe ".elevated" do + let(:eusername) { double("elevatedusername") } let(:username) { double("username") } + let(:failed_output) { WinRM::Output.new.tap { |out| + out.exitcode = -196608 + out << {stderr: "(10,8):UserId:"} + out << {stderr: "At line:72 char:1"} + } } before do - allow(subject).to receive(:elevated_username).and_return(username) + allow(subject).to receive(:elevated_username).and_return(eusername) allow(shell).to receive(:username).and_return(username) allow(shell).to receive(:username=) end @@ -102,12 +108,20 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError) end - it "should use elevated username" do - expect(subject).to receive(:elevated_username).and_return(username) + it "should use elevated username and retry on username failure" do + expect(subject).to receive(:elevated_username).and_return(eusername) + expect(shell).to receive(:run).with("dir").and_return(failed_output) expect(shell).to receive(:run).with("dir").and_return(output) expect(shell).to receive(:interactive_logon=).with(false) expect(subject.elevated("dir").exitcode).to eq(0) end + + it "should not retry on username failure if elevated username is the same" do + expect(subject).to receive(:elevated_username).and_return(username) + expect(shell).to receive(:run).with("dir").and_return(failed_output) + expect(shell).to receive(:interactive_logon=).with(false) + expect(subject.elevated("dir").exitcode).to eq(failed_output.exitcode) + end end describe ".cmd" do @@ -215,8 +229,8 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") end - it "should only compute elevated username once" do - expect(subject).to receive(:powershell).once.with(/computername/).and_yield(:stdout, "COMPUTERNAME") + it "should compute elevated username every time" do + expect(subject).to receive(:powershell).twice.with(/computername/).and_yield(:stdout, "COMPUTERNAME") expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}") end