posted at 2022.2.5 15:04 by 风信子
在Windows系统中有一个命令行程序CMD,是进入命令提示符的一个纽带。对于从事计算机业务的用户来说很熟悉。Linux也有同类型的程序,例如Bash、Teminal。这些程序通常称之为Terminal(终端)或Shell。
在Windows系统下,CMD相当于在Windows窗口使用的DOS系统,CMD可以做一些在Windows中做不了的工作,有些时候一些问题必须在CMD下解决。简单地说,CMD就是通过命令行实现通过键盘鼠标操作才能完成的一切操作。
本文介绍的CMD显现是指通过编写程序代码在用户计算机上执行CMD命令并将执行回传,以获取执行后的操作结果。
一、原理
要将CMD显现,须解决进程间通信问题。
对于进程间的通信,可以利用四种方式来实现:
第一种是利用剪贴板实现本机进程间的通信。
第二种是利用邮槽实现本机或跨网络进程间的通信。
第三种是利用匿名管道实现本机父子进程之间的通信。
第四种是利用命名管道实现本机或跨网络进程间的通信。
管道是一种用于在进程间共享数据的机制,其实质是一段共享内存。Windows系统为这段共享的内存设计采用数据流I/0的方式来访问。由一个进程读、另一个进程写,类似于一个管道两端,因此这种进程间的通信方式称作“管道”。
管道分为匿名管道和命名管道。
匿名管道只能在父子进程间进行通信,不能在网络间通信,而且数据传输是单向的,只能一端写,另一端读。命名管道可以在任意进程间通信,通信是双向的,任意一端都可读可写,但是在同一时间只能有一端读、一端写。
创建匿名管道的方式,可以获取CMD在本机的执行结果。
具体可以分成以下 5 个步骤:
初始化匿名管道的安全属性结构体SECURITY_ATTRIBUTES,使用匿名管道默认的缓冲区大小,并调用函数CreatePipe创建匿名管道,获取管道数据读取句柄和管道数据写入句柄。
对即将创建的进程结构体STARTUPINFO进行初始化,设置进程窗口隐藏,并把上面管道数据写入句柄赋值给新进程控制台窗口的缓存句柄,这样,新进程会把窗口缓存的输出数据写入到匿名管道中。
开始调用CreateProcess函数创建新进程,执行 CMD 命令,并调用函数WaitForSingleObject等待命令执行完毕。
命令执行完毕后,便调用ReadFile函数根据匿名管道的数据读取句柄从匿名管道的缓冲区中读取缓冲区的数据,这个数据就是新进程执行命令返回的结果数据。
这样,经过上面 4 步操作,CMD 程序的执行结果就成功获取了。这是就可以关闭句柄,释放资源了。
二、支持函数
在Windows系统中有很多WIN32API函数可以执行CMD命令,例如system、WinExg、reateProcess等,但是这些函数均不能获取执行后的操作结果。所以, CMD显现的关键是获取CMD的执行结果。
CreatePipe 函数:
CMD显现只需要建立匿名管道。
函数声明
BOOL WINAPI CreatePipe(_Out_PHANDLE hReadPipe,_Out_PHANDLE hWritePipe,_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,_In_DWORD nSize);
参数
hReadPipe[out]返回一个可用于读管道数据的文件句柄。
hWritePipe[out]返回一个可用于写管道数据的文件句柄。
lpPipeAttributes[in, optional]传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进程继承。如果传NULL,则返回的句柄是不可继承的。该结构的lpSecurityDescriptor成员用于设定管道的安全属性,如果传NULL,那么该管道将获得一个默认的安全属性,该属性与创建该管道的用户账户权限ACLs的安全令牌(token)相同。nSize[in]管道的缓冲区大小。但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默认的缓冲区大小。
返回值
如果函数成功,则返回值不为零。如果函数失败,返回值为零。要获取扩展错误信息,请调用GetLastError。
三、源代码
PipeCmd.cpp
#include "stdafx.h"#include "PipeCmd.h" void ShowError(char *pszText){ char szErr[MAX_PATH] = {0}; ::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError()); ::MessageBox(NULL, szErr, "ERROR", MB_OK);} // 执行 cmd 命令, 并获取执行结果数据BOOL PipeCmd(char *pszCmd, char *pszResultBuffer, DWORD dwResultBufferSize){ HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL; SECURITY_ATTRIBUTES securityAttributes = {0}; BOOL bRet = FALSE; STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; // 设定管道的安全属性 securityAttributes.bInheritHandle = TRUE; securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; // 创建匿名管道 bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0); if (FALSE == bRet) { ShowError("CreatePipe"); return FALSE; } // 设置新进程参数 si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.wShowWindow = SW_HIDE; si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; // 创建新进程执行命令, 将执行结果写入匿名管道中 bRet = ::CreateProcess(NULL, pszCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); if (FALSE == bRet) { ShowError("CreateProcess"); } // 等待命令执行结束 ::WaitForSingleObject(pi.hThread, INFINITE); ::WaitForSingleObject(pi.hProcess, INFINITE); // 从匿名管道中读取结果到输出缓冲区 ::RtlZeroMemory(pszResultBuffer, dwResultBufferSize); ::ReadFile(hReadPipe, pszResultBuffer, dwResultBufferSize, NULL, NULL); // 关闭句柄, 释放内存 ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); ::CloseHandle(hWritePipe); ::CloseHandle(hReadPipe); return TRUE;}
Pipe_CMD_Test.cpp
// Pipe_CMD_Test.cpp : 定义控制台应用程序的入口点。// #include "stdafx.h"#include "PipeCmd.h" int _tmain(int argc, _TCHAR* argv[]){ char szCmd[] = "tracert 45.207.55.251"; //ping 45.207.55.251 char szResultBuffer[512] = {0}; DWORD dwResultBufferSize = 512; // 执行 cmd 命令, 并获取执行结果数据 if (FALSE == PipeCmd(szCmd, szResultBuffer, dwResultBufferSize)) { printf("pipe cmd error.\n"); } else { printf("CMD执行结果为:\n%s\n", szResultBuffer); } system("pause"); return 0;}
这个显现程序不单单可以获取CMD命令行窗口的执行数据,也可以获取其它控制台的执行数据。在执行CMD命令的时候,要调用WaitForSingleObject函数来等待执行完毕,这样才能获取执行结果。这里并没有体现出远程,要想实现远程需要将要执行的CMD显现通过网络进行传输。执行完毕后,将执行结果传输出去即可。传输部分不再介绍。
四、效果
c4b49d1a-c83f-421d-8c37-943a09c5f2ca|0|.0|96d5b379-7e1d-4dac-a6ba-1e50db561b04
Tags: 程序, 代码, 类, 数据
IT技术