【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开

Ghjconan在他的博客发表了文章《一次moveuser的使用经历》,原帖地址:

http://blogs.itecn.net/blogs/ghjconan/archive/2008/11/08/moveuser.aspx

一次moveuser的使用经历

有一段时间没再ITECN上更新了,主要因为工作上要完成OCS的SOP文档,所以把主要精力都投入在上面了。

前昨两天接到一个任务,需要帮助一些没有加域的计算机加域,并迁移用户的相关设置。经过接近一天时间的努力,用脚本配合moveuser,顺利完成了工作组下的用户平滑迁移到域用户,收获颇多,在此和诸位分享。也许在编写脚本的过程中,某些地方还有欠缺,还请各位多多包含。

先来描述下问题情境:
1. 用户使用本地计算机Users组成员帐号(假定用户名为user)登录计算机,并且使用了一段时间,用户配置文件偏大。且本地管理员密码不统一,可选的密码有三个。已分配IP地址。
2. 现在计划将这些用户批量加域,计算机需要重命名,并迁移用户的相关设置。
3. 整个过程对用户产生的影响越小越好,Helpdesk要进行的操作越少越好。
4. 所有脚本最好整合成WinRAR自解压包。

在描述具体实现过程前,诸位可以下载以下视频,查看最终效果。

从 SkyDrive 下载

下面来说说具体解决步骤。

首先第一个问题是,用户是以本地计算机Users组成员身份登录计算机的,在这种情况下,我们是没有办法使用他的用户来加域的,需要使用本地管理员组里的成员,如Administrator才行。但是这又引出另一个问题,管理员帐号密码不统一,我们需要使用一种方法使得脚本能遍历这三种可能性,最终用正确的密码以管理员身份运行重命名计算机的脚本。如果三个密码都无法使用,那只能给个提示劳驾Helpdesk的兄弟破解密码了。

那么如何实现以上需求呢?VBScript当然无法实现,他没有提供内置的方法来让我们以其它用户身份运行程序或脚本,而且遍历管理员密码也很难实现。那么我们就只能面对出师未捷身先死的窘境了么?当然不是,这个世界如此丰富多彩,早已有高人给我们提供了其它工具来实现这些需求,下面请出的是AutoIT,先来看看最终代码:

   1: dim $pwd[3]
   2: $pwd[0] = "pass@w0rd1"
   3: $pwd[1] = "pass@w0rd2"
   4: $pwd[2] = "pass@w0rd3"
   5:  
   6: For $i = 0 to 2
   7:         $retVal = RunAs("Administrator",@ComputerName,$pwd[$i],1,"wscript.exe " & Chr(34) & @ScriptDir & Chr(34) & "\renamecomputer.vbs")
   8:         If $retVal = 0 Then
   9:             if $i+1 = 3 Then
  10:                 MsgBox (0+16,"错误","管理员密码错误,请重设本地管理员密码。")
  11:                 ExitLoop
  12:             EndIf
  13:         Else
  14:             ExitLoop
  15:         EndIf
  16: Next

有关AutoIT语法的相关内容,我在这里就不在阐释了,有兴趣的朋友可以去找找相关资料。在以上代码中,我们首先声明一个数组来存放可能的管理员密码。接着我们使用For循环来遍历可能的管理员密码,如果三个已知密码都是错误的,那么我们会给Helpdesk以下提示: 
【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志

如果三个密码中有一个正确,就会启动重命名计算机脚本(与AutoIT生成的程序在同一路径下,所以使用@ScriptDir常量,并且为了避免路径中的空格引发错误,所以使用了Chr(34)。而路径中有空格是因为将涉及到的文件都解压到%temp%目录下)

下面来说说重命名计算机的脚本。

   1: Set objShell = CreateObject("Wscript.shell")
   2: Set objFSO = CreateObject("Scripting.FileSystemObject")
   3:  
   4: strComputerName = InputBox("请输入新计算机名","输入")
   5: strLocalUserName = InputBox("请正确输入当前用户名" & vbcrlf & "否则将影响到用户配置文件迁移!","输入")
   6: strDomainUserName = InputBox("请正确输入域账号,姓名的拼音,比如lisi" & vbcrlf & "否则将影响到用户配置文件迁移!","输入")
   7: strScriptpath = Replace(WScript.ScriptFullName,WScript.ScriptName,"")
   8:  
   9: Set objTXT = objFSO.CreateTextFile(strScriptpath & "\moveuser.bat")
  10: objTXT.WriteLine("moveuser " & strLocalUserName & " contoso\" & strDomainUserName & " /k")
  11: objTXT.Close
  12:  
  13: objShell.Run("cmd /c net user administrator abcd@123")
  14:  
  15: WScript.Sleep 1500
  16:  
  17: strComputer = "."
  18: Set objWMIService = GetObject("winmgmts:" _
  19:     & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
  20:  
  21: Set colComputers = objWMIService.ExecQuery _
  22:     ("Select * from Win32_ComputerSystem")
  23:  
  24: For Each objComputer in colComputers
  25:     strErr = objComputer.Rename(strComputername)
  26: Next
  27:  
  28: If strErr = 0 Then
  29:         objFSO.CopyFile strScriptpath & "\joindomain.vbs","c:\windows\system32\"
  30:         objFSO.CopyFile strScriptpath & "\moveuser.bat","c:\windows\system32\"
  31:         objFSO.CopyFile strScriptpath & "\moveuser.exe","c:\windows\system32\"
  32:         objshell.RegWrite "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce\joindomain", "c:\windows\system32\joindomain.vbs","REG_SZ"
  33:         MsgBox "单击确定重新启动计算机",vbOKOnly+vbInformation,"信息"
  34:         objshell.Run("cmd /c shutdown.exe -r -t 0")
  35: End If

首先我们需要Helpdesk输入新计算机名,当前用户的登录名,将要使用的域账号名,这也是整个迁移过程中唯一需要用户输入的地方。当然这里使用简单的输入框会存在一点问题,如果单击输入框中的取消按钮,可能会导致整个加域任务失败。不过这个任务是由Helpdesk完成而不是普通用户,所以我们暂时不处理这个问题。接下来,我们取得当前脚本的运行路径,并在该路径下根据Helpdesk的输入信息来生成moveuser.bat供后续使用。接着我们需要做的是重设本地管理员的密码,当下一次重启时,Helpdesk需要使用管理员帐号登录,加域脚本会自动执行加域脚本和 moveuser.bat。

接下来我们利用脚本中心提供的示例代码重命名计算机,并将加域脚本,包含moveuser命令的批处理及moveuser命令本身复制到 system32目录下。同时我们向注册表里写入相关信息,使得下次登录时加域脚本能自动运行。最后脚本执行完成,然后单击确定后重新启动计算机。

【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志

重启完成后,Helpdesk将使用管理员帐号登录计算机,计算机会自动执行加域脚本joindomain.vbs。

【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志

加域脚本中的代码也不太复杂,下面先贴上代码:

   1: Const JOIN_DOMAIN = 1
   2: Const ACCT_CREATE = 2
   3:  
   4: strdnsOne = "192.168.100.1"'InputBox("Please type your first DNS address","Input")
   5: strdnsTwo = "192.168.100.2"'InputBox("Please type your second DNS address","Input")
   6: strDomain = "contoso.com"'InputBox("Please type your domain name","Input")
   7: strUser = "administrator"'InputBox("Please type your domain user name","Input")
   8: strPassword = "abcd@123"'InputBox("Please type you domain user's password","Input")
   9:  
  10: strComputer = "."
  11:  
  12: Set objShell = CreateObject("Wscript.shell")
  13: Set objFSO = CreateObject("Scripting.FileSystemObject")
  14:  
  15: Set objWMIService = GetObject("winmgmts:" _
  16:     & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
  17:  
  18: Set colNetCards = objWMIService.ExecQuery _
  19:     ("Select * From Win32_NetworkAdapterConfiguration Where IPEnabled = True")
  20:  
  21: For Each objNetCard in colNetCards
  22:     arrDNSServers = Array(strdnsone, strdnstwo)
  23:     objNetCard.SetDNSServerSearchOrder(arrDNSServers)
  24: Next
  25:  
  26: Set objNet = CreateObject("Wscript.network")
  27: strComputerName = objnet.ComputerName
  28:  
  29: Set objComputer = GetObject("winmgmts:{impersonationLevel=Impersonate}!\\" & _
  30: strComputer & "\root\cimv2:Win32_ComputerSystem.Name='" & _
  31:         strComputername & "'")
  32:  
  33: ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain, _
  34:     strPassword, strDomain & "\" & strUser, NULL, _
  35:         JOIN_DOMAIN + ACCT_CREATE)
  36:  
  37: If ReturnValue = 0 Then
  38:     Set objCMD = objshell.Exec("c:\windows\system32\moveuser.bat")
  39:     Do While objCMD.Status = 0
  40:         WScript.Sleep 500
  41:     Loop
  42:     MsgBox "单击确定重新启动计算机",vbOKOnly+vbInformation,"信息"
  43:     objshell.Run("cmd /c shutdown.exe -r -t 0")
  44: End If

首先声明加域脚本要用到的常量,接着我们将加域所用到的dns服务器地址,域名,加域用的帐号名和密码(实验环境中直接用的域管理员帐号,实际环境中我们当然不能用,可以使用一个专门的帐号来代替)存放在变量中。下面使用的代码来自脚本中心,我们只需根据具体环境相应修改即可。当加域成功后,脚本会运行之前创建的moveuser批处理来迁移用户配置文件,当迁移完成后,需要再次重启计算机。至此整个任务已经完成,接下来需要做的就是等待计算机重启完成后,使用域账号登录,验证是否达到了我们预想的效果。 
【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志  
【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志

总结几点:

1. 一开始的输入是关键,在使用这整套脚本之前,我们需要和Helpdesk强调这一点
2. 我们需要保证软硬件环境能使得加域成功,现有脚本中没有做任何处理
3. 用WinRAR生成自解压包时要注意解压路径的选择(start.exe即是用AutoIT写得脚本) 
【转】从 Ghjconan 的《一次 moveuser 的使用经历》谈开 - 黑暗圣堂 - 圣堂日志

4. 关于虚拟机环境。诸位注意,我用的是Windows Server 2003做的实验,但是如果你在Windows Server 2003中创建普通帐号,这个账号是没有办法是没有重新启动计算机的,所以记得在本地安全策略里修改关闭系统的权限。

最后还是强调一句:

文中所有代码仅供参考,如需测试请在虚拟机中执行。本文的目的旨在提供思路而非最终解决方案。

正好最近正在做一个项目,是活动目录基础架构,花费的大部分时间是加域。于是我就将Ghjconan的设想在我的虚拟机上实验了下。

1、如果源用户是本地administrator

     现在有些单位直接采用的网上流行的GHOST镜像安装系统,结果导致终端用户使用的就是administrator。 目标用户将拥有administrator的配置文件,结果该用户具有了本地管理员的权限!

2、Moveuser的原理

    两个月前,我就对用户配置文件做个一次测试。我检查了下Moveuser的工作原理,我发现了几个系统修改的地方。

    a、Moveuser修改了注册表路径HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList下源用户SID项项值改成了目标用户的SID。

    b、修改了源用户在document and setting下的文件夹权限,去掉了源用户,添加了目标用户,并给予了完全控制的权限。

    c、源用户登陆不能在使用他原来的配置文件了,他将从default user中复制配置文件。

 

转自:http://blogs.itecn.net/blogs/smileruner/archive/2008/11/14/ghjconan-moveuser.aspx