(二)在Windows脚本中使用批处理或程序
---------------
这些东西一直没整理好发出去,这才发觉一晃好多年过去了,对我个人的来说这些功能其实没有多大实用意义,好多内容可能别人的帖子里面也有提及的,但还是贴出来了却以往的一个心结吧。
需要说明的就是,本人的用词可能不是很科学或者说精准,对于某些原理之类的东西也一般没有去深究,也不会再文中大量说明,第一是关于各种资料性的文档仅仅本论坛发过的应该就有很多了,再重复没有意义,第二点,也是最重要的一点就是,我不是来搞研究的,用脚本只是想要把电脑弄的好玩一点,所以我主要精力放在实际结果上了,细节的原理有些也不太明白,说错了误导就不好了,作为一个工具我需要用到什么功能的时候再去查就行。
在脚本中可以创建 WScript.Shell 的对象,其中有 Run 和 Exec 方法可以运行外部程序。其中 Run 最典型的例子就是
1. 有参数可以控制窗口的状态,可以用于隐藏运行程序。比如
隐藏运行批处理 http://bbs.bathome.net/thread-5131-1-97.html
2. 自动登录QQ,自动XXXXXX。原理就是启动一个程序,然后模拟按键进行输入,模拟按键需要 Sendkeys 函数支持,论坛里面已经有很多帖子了,比如
启动记事本进行操作 http://bbs.bathome.net/thread-1380-1-1.html
看起来像是脚本于程序交互?其实这种方式很不稳定,我也是在最开始装13的时候用过,自动安装腾讯QQ2007。延时有点问题或者遇到焦点跑掉就可能产生无法遇到的后果。
哈哈,对于焦点跑掉的情况,基本上都是用一个 AppActivate "标题" 的方法去重新进行激活到最前,但有很多缺陷,比如有重名的可能不准确,有些窗口未知或者会变的。
如果用 Exec 方法启动的话,就能获取到该进程的 PID ,然后可以进行精确的激活,比如 | Set ws = WScript.CreateObject("WScript.Shell") | | Set ps = ws.Exec("C:\WINDOWS\notepad.exe") | | WScript.Sleep 1000 | | WScript.Echo "已启动记事本,进程ID为 " & ps.ProcessID | | | | WScript.Sleep 5000 | | ws.AppActivate ps.ProcessIDCOPY |
方法 Exec 另一个不同之处是启动之后可以得到它的标准输入输出,但是没法进行可视界面操作。用来得到命令行的输出结果还是很方便的。
来看个简单的例子把,如果你刚刚学了最基本VBS脚本语法,你可能不知道怎样获取计算机当前登录用户名和处理器信息呢,但是想到在命令行里面不是一个变量就搞定了吗
EXP02U.BAT@echo %username%COPY 我们可以调用这个批处理,然后获取它的输出,不就得到信息了吗,于是有
EXP02U.VBS | Dim sUser, ws, bat, batout | | | | Set ws = WScript.CreateObject("WScript.Shell") | | | | Set bat = ws.Exec("EXP02U.BAT") | | | | Set batout = bat.StdOut | | | | sUser = batout.ReadAll() | | | | WScript.Echo "用户名是:" & sUserCOPY |
如果要获取处理器信息,换成这样@set|find "PROCESSOR"COPY 那我们怎么不能一次就获取两个呢?改成这样
EXP02UP.BAT | @echo %username% | | @set|find "PROCESSOR"COPY |
这时输出的就不能一股脑的全部获取了,要分别区分,第一行是用户名,剩下的是处理器信息
EXP02UP.VBS | Set ws = WScript.CreateObject("WScript.Shell") | | Set bat = ws.Exec("EXP02UP.BAT") | | Set batout = bat.StdOut | | sUser = batout.ReadLine() | | sTemp = batout.ReadAll() | | WScript.Echo "登录人:" & vbCrLf & sUser | | WScript.Echo "处理器信息:" & vbCrLf & sTempCOPY |
等等,刚才运行过那两个批处理文件都是一闪而过啊,完全没有看到是什么东西,我们改成这样吧
EXP02UP.BAT 第二版 | @echo off | | echo %username% | | set|find "PROCESSOR" | | pause>nulCOPY |
嘿,行了,这才像是批处理嘛,多亲切的黑框框,按下回车结束它。
然后在运行一下我们的VBS脚本,咦,怎么一直就是个黑框,什么也没有,难道是卡住了?
等了一分钟,算了,估计出不来了,点X关闭吧,嘿,真行了,脚本又有提示了。
这是怎么回事呢?原来 ReadAll 方法想要读取全部内容,但是输出流在 pause 的时候还没有结束,于是要等到我们 “随便按一下”,也就是输入一个任意字符,这时批处理才会 “自杀” 以结束,才能得到输出流全部的内容。
黑的什么也没有,就是因为标准输出流被脚本“接管”了;我们使劲按键盘也不会结束,因为标准输入也被“接管”了,外面怎么按都没反映啦。
但是在“卡住”的时候,它已经将我们需要的信息都输出了,所以,在我们百般无奈把它 “强X” 的时候,所有的一切自然都断开玩完了,脚本也认为程序的输出结束了,就将之前已经有的部分读取到了。
难道我们只能用这么野蛮的办法吗?当然不是啦,刚才不是已经用过 StdOut 么,还有个输入的 StdIn 没用呢,既然你要按(其实人家不是要按啦,不要Sendkey哦,而是要从标准输入流中接收一个字符),那我们就给你吧,在脚本里面加一句,这样就可以 “圆寂” 啦
EXP02UP.VBS 第二版 | Set ws = WScript.CreateObject("WScript.Shell") | | Set bat = ws.Exec("EXP02UP.BAT") | | | | Set batin = bat.StdIn | | | | batin.Write "Spring Brother" | | Set batout = bat.StdOut | | sUser = batout.ReadLine() | | sTemp = batout.ReadAll() | | WScript.Echo "登录人:" & vbCrLf & sUser | | WScript.Echo "处理器信息:" & vbCrLf & sTempCOPY |
你不是说输入“一个”字符么,怎么那么多个。。。还有,为什么输入放到那么前面,没关系吗?
试试就知道了,估计多了的别人也不管你吧。
其实你在控制台运行命令的时候有没有发现过,如果一个命令正在执行中的还未结束的时候,你输入另外一个命令并且回车,那么这个命令会在当前过程结束后马上执行的,比如你正在执行dir /s C:\*.sbCOPY 这可能会花一些时间,不要紧,你正好可以慢慢的在键盘敲入echo %time%COPY 并且回车,现在还卡着没有反映?如果不想再等查找了,按下 CTRL+C 将其强制结束,你会发现之后的显示时间其实是执行了的,如果你还在怀疑后面这句是在当时按下回车就执行了,还是强制结束查找之后才执行的,看看那个时间就清楚啦。那个 StdIn.Write 干的事就跟你刚才敲键盘的效果一样,所以你可以理解它为什么放哪里都没关系了。
但是这样也许也不靠谱,如果批处理执行过程中有些不可遇到的异常,你输入一堆东西它可能也不会结束,所以,我们就用提供的 Terminate 方法私下决定给当事人一个 “安乐死” 吧
EXP02UP.VBS 第三版 | Set ws = WScript.CreateObject("WScript.Shell") | | Set bat = ws.Exec("EXP02UP.BAT") | | | | WScript.Sleep 333 | | | | bat.Terminate | | Set batout = bat.StdOut | | sUser = batout.ReadLine() | | sTemp = batout.ReadAll() | | WScript.Echo "登录人:" & vbCrLf & sUser | | WScript.Echo "处理器信息:" & vbCrLf & sTempCOPY |
写批处理代码起家的,有没有觉得那个BAT文件还不完美啊,一点都不灵活对吧,弄成一个可以灵活选择需要什么信息就输入相应代码的怎么样:
EXP02UPT.BAT | @echo off | | :Spring | | echo What can I do for you? | | echo 1.用户名 2.处理器信息 3.日期时间 0.退出 | | set "Brother=" | | set /p Brother= | | if [%Brother%]==[0] GOTO :EOF | | if [%Brother%]==[1] GOTO :U | | if [%Brother%]==[2] GOTO :P | | if [%Brother%]==[3] GOTO :T | | echo *** ERROR *** | | GOTO :Spring | | | | :U | | echo %username% | | endlocal | | GOTO :Spring | | | | :P | | set|find "PROCESSOR" | | GOTO :Spring | | | | :T | | echo %date% %time% | | GOTO :SpringCOPY |
试了一下,功能强大啊,可以无限执行下去,所以要实时的时间都可以一直取的到哦。
那我的脚本要依次读取 时间、用户名、处理器信息 该怎么写呢?
EXP02UPT.VBS | Set ws = WScript.CreateObject("WScript.Shell") | | Set bat = ws.Exec("EXP02UPT.BAT") | | Set batin = bat.StdIn | | Set batout = bat.StdOut | | batin.Write "3" & vbCr | | WScript.Sleep 333 | | batin.Write "1" & vbCr | | WScript.Sleep 333 | | batin.Write "2" & vbCr | | WScript.Sleep 333 | | batin.Write "0" & vbCr | | WScript.Sleep 333 | | sAll = batout.ReadAll() | | WScript.Echo sAllCOPY |
加延时是因为需要输入间隔么,还是需要给他一点处理时间,这我不太清楚了,反正用 vbCrLf 好像有些问题。哎,得到的结果就是一整砣,批处理那些没用的提示信息也混在一起,不好拆出来啊,我都懒得写了,有兴趣的自己试试吧,就当字符串操作练手。
我们还有一个标准错误输出流可以用啊,既然不好剔除无用信息,那我们就分通道吧
在批处理中指定提示信息还是默认的 1 通道,把需要的结果放到 2 通道,也就是一般用于输出错误信息的(你管我这是不是错误信息呢,我的地盘我做主)
EXP02UPT.BAT 第二版 | @echo off | | :Spring | | echo What can I do for you? | | echo 1.用户名 2.处理器信息 3.日期时间 0.退出 | | set "Brother=" | | set /p Brother= | | if [%Brother%]==[0] GOTO :EOF | | if [%Brother%]==[1] GOTO :U | | if [%Brother%]==[2] GOTO :P | | if [%Brother%]==[3] GOTO :T | | echo *** ERROR *** | | GOTO :Spring | | | | :U | | >&2 (echo %username%) | | endlocal | | GOTO :Spring | | | | :P | | >&2 (set|find "PROCESSOR") | | GOTO :Spring | | | | :T | | >&2 (echo %date% %time%) | | GOTO :SpringCOPY |
然后VBS运行它的时候,我们就不管 StdOut 里面的东西了,从 StdErr 里面获取纯的想要的信息
EXP02UPT.VBS 第二版 | Set ws = WScript.CreateObject("WScript.Shell") | | Set bat = ws.Exec("EXP02UPT.BAT") | | Set batin = bat.StdIn | | Set baterr = bat.StdErr | | batin.Write "3" & vbCr | | WScript.Sleep 333 | | batin.Write "1" & vbCr | | WScript.Sleep 333 | | batin.Write "2" & vbCr | | WScript.Sleep 333 | | bat.Terminate | | sTime = baterr.ReadLine() | | sUser = baterr.ReadLine() | | sProccesor = baterr.ReadAll() | | WScript.Echo "时间:" & sTime | | WScript.Echo "用户:" & sUser | | WScript.Echo "处理器:" & vbCrLf & sProccesorCOPY |
忽然灵机一动,如果用 Exec 运行 FTP 是不是就可以实现模拟人敲命令自动上传下载了呢?
呵呵,直到这里都讨论的是“标准”输入输出,对于“不标准”的,可能就爱莫能助了,谁知道哪些标准哪些不标准呢,你可以自己去试试,也可以搜搜相关的讨论,我就不废话太多了,毕竟实践才是检验真理的唯一标准啊。
能不能摒弃 WScript.Sleep 延时语句呢,每次看到有这个东西第一感觉就是不专业!
如果想要实现根据第一次获取时间,如果是奇数秒就获取用户名,如果是偶数就获取处理器信息,又怎么弄呢?
输出流也有 Read() 方法,用 Read 或者 ReadLine 逐字读取分析就好了,感觉这些都没什么实用性,就不写在这里了,有兴趣的可以参看附件中内容。
回头一看,这一大串实例都是由想要在 VBS 中获取 %username% 但又不会写引起的,对于环境变量可以这样获取 | Set WshShell = WScript.CreateObject("WScript.Shell") | | Set WshEnv = WshShell.Environment("PROCESS") | | sUser = WshEnv("username") | | MsgBox sUserCOPY |
环境变量类型也有几种,其中 SYSTEM 和 USER 类型的都保存在计算机上,在注册表 HKEY_CURRENT_USER\Environment\ 路径可以看到用户环境变量,而在 PROCESS 中设置的只在运行时有效,有兴趣的可以去查查相关文档。
不管是用批处理来调用VBS脚本,还是在VBS中启动外部程序,都可以看成是一个程序甲调用另一个程序乙,对于在甲中设置的环境变量,是可以被乙读取到的,在乙启动之后甲修改值不会对乙有影响,乙中的更改也不会对甲起作用。
最后还是来做一个轻松的小玩意结束吧。
cgcym.bat | @echo off | | set cgcym=春 哥 纯 爷 们 | | for %%a in (%cgcym%) do call echo %%a%%%%a%% | | pause>nulCOPY |
看看这个批处理运行起来是什么样的。然后我们在写个 VBS 来启动它。
txzhz.vbs | Set ws = WScript.CreateObject("WScript.Shell") | | Set envp = ws.Environment("PROCESS") | | envp("春") = Chr(8) & "铁" | | envp("哥") = Chr(8) & "血" | | envp("纯") = Chr(8) & "真" | | envp("爷") = Chr(8) & "汉" | | envp("们") = Chr(8) & "子" | | ws.Run "cgcym.bat"COPY |
怎么样,是不是很好玩。
参考资料:
Run Method (Windows Script Host)
http://msdn.microsoft.com/en-us/library/d5fk67ky
AppActivate Function
http://msdn.microsoft.com/en-us/library/dyz95fhy
Exec Method (Windows Script Host)
http://msdn.microsoft.com/en-us/library/ateytk4a
WshScriptExec Object
http://msdn.microsoft.com/en-us/library/2f38xsxe
Environment Property
http://msdn.microsoft.com/en-us/library/fd7hxfdd |