如何判断当前程序是直接运行还是从CMD窗口中运行(上)

前几天在写console程序的时候忽然想起来“如何判断当前程序是直接运行还是从CMD窗口中运行”的问题,网上搜索了一下,并无明确结果,为此我请教了一下天杀,他给我出了2个办法。第一个办法是通过GetStartupInfo得到当前窗口的 Title,从下图可以看到直接运行的时候是只有文件名,如果从cmd下面运行会有完整的路径和文件名

result

例程:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
windows;

var
Info:STARTUPINFO;
begin
GetStartupInfo(Info);
writeln(Info.lpTitle);
readln;
end.

上面 STARTUPINFO结构体的 dwFlags 位也能反映这个差别:

STARTF_TITLEISLINKNAME 0x00000800
The lpTitle member contains the path of the shortcut file (.lnk) that the user invoked to start this process. This is typically set by the shell when a .lnk file pointing to the launched application is invoked. Most applications will not need to set this value.
This flag cannot be used with STARTF_TITLEISAPPID.

完整程序下载 cmd1

检查当前可执行文件是否为64位的工具

检查当前可执行文件是否为64位的工具

很多时候我们经常发现拿到手的某个EXE或者EFI文件无法执行,出现的错误信息也很少,无法得知是因为文件损坏还是这个程序只能在64位Windows下运行。

因此编写了一个简单的工具用来检查EXE或者EFI程序是否是64位的。

用法很简单:直接拖拽到cc64.exe 上或者在命令行下用 cc64 文件名来运行。

原理上是分析PE结构,Machine Types 字段,有如下定义

Constant Value Description
IMAGE_FILE_MACHINE_UNKNOWN 0x0 The contents of this field are assumed to be applicable to any machine type
IMAGE_FILE_MACHINE_AM33 0x1d3 Matsushita AM33
IMAGE_FILE_MACHINE_AMD64 0x8664 x64 《—————– 64 bit
IMAGE_FILE_MACHINE_ARM 0x1c0 ARM little endian
IMAGE_FILE_MACHINE_ARMV7 0x1c4 ARMv7 (or higher) Thumb mode only
IMAGE_FILE_MACHINE_EBC 0xebc EFI byte code
IMAGE_FILE_MACHINE_I386 0x14c Intel 386 or later processors and compatible processors《———– 32 bit
IMAGE_FILE_MACHINE_IA64 0x200 Intel Itanium processor family 《———– 64 bit
IMAGE_FILE_MACHINE_M32R 0x9041 Mitsubishi M32R little endian
IMAGE_FILE_MACHINE_MIPS16 0x266 MIPS16
IMAGE_FILE_MACHINE_MIPSFPU 0x366 MIPS with FPU
IMAGE_FILE_MACHINE_MIPSFPU16 0x466 MIPS16 with FPU
IMAGE_FILE_MACHINE_POWERPC 0x1f0 Power PC little endian
IMAGE_FILE_MACHINE_POWERPCFP 0x1f1 Power PC with floating point support
IMAGE_FILE_MACHINE_R4000 0x166 MIPS little endian
IMAGE_FILE_MACHINE_SH3 0x1a2 Hitachi SH3
IMAGE_FILE_MACHINE_SH3DSP 0x1a3 Hitachi SH3 DSP
IMAGE_FILE_MACHINE_SH4 0x1a6 Hitachi SH4
IMAGE_FILE_MACHINE_SH5 0x1a8 Hitachi SH5
IMAGE_FILE_MACHINE_THUMB 0x1c2 ARM or Thumb (“interworking”)
IMAGE_FILE_MACHINE_WCEMIPSV2 0x169 MIPS little-endian WCE v2

64Bit .EXE and .EFI file checking utilty

This is autility which can be use to check if a EXE or EFI is a64bit image.

Usage: Drag and drop the file to this utilty in Windows file manager or run cc64 filename in the console window.

It will check the PE header when running. In the Machine type of PE header, you can find the the specified CPU that the PE image can be run.

cc64

www.lab-z.com
zoologist
2013-03-28

使用 Outlook VBA 批量保存MSG格式邮件

使用下面的程序,可以实现将选中的邮件以MSG的格式批量保存到指定的目录下。每个文件会以收到的时间命名。

Function DateToString(d As Date) As String
Dim s As String
s = Format([d], “yyyymmddhhnnss”)
DateToString = s
End Function

Sub SaveSelMails()
Dim objItem As Object
Dim strPath As String
Dim strFilename As String

‘Exit if you don’t choose any files
If ActiveExplorer.Selection.Count = 0 Then Exit Sub

strPath = InputBox(“Please enter the full path:”, “Enter path”)

‘write all the chosen emails to the path
For Each objItem In ActiveExplorer.Selection
strFilename = DateToString(objItem.ReceivedTime)
objItem.SaveAs strPath & strFilename & “.msg”, olMSGUnicode
Next

MsgBox “Email text extraction completed!”, vbOKOnly + vbInformation, “DONE!”
Set objItem = Nothing
End Sub

需要注意的是,输入时要输入以 ‘\’ 结尾的字符串,比如: ‘c:\tmp\’

此外,还有更简单的方式:在Outlook中使用Ctrl+左键选中邮件之后,直接拖拽到目录中。

关于 Outlook 选择目录对话框的问题

需要在 Outlook 下通过VBA打开一个选择目录的对话框,刚开始搜索到的解决方案类似如下代码

Dim msDialog As Office.FileDialog
Set msDialog = Application.FileDialog(msoFileDialogFilePicker)
msDialog.Show

但是经过很多次实验,Outlook 2010中根本没有 Application.FileDialog 这样的属性方法。这条路是无法行通的!!!

最后的解决方案是如下代码

Private Type BROWSEINFO
hOwner As Long
pidlRoot As Long
pszDisplayName As String
lpszTitle As String
ulFlags As Long
lpfn As Long
lParam As Long
iImage As Long
End Type

Private Declare Function SHGetPathFromIDList Lib “shell32.dll” Alias _
“SHGetPathFromIDListA” (ByVal pidl As Long, _
ByVal pszPath As String) As Long

Private Declare Function SHBrowseForFolder Lib “shell32.dll” Alias _
“SHBrowseForFolderA” (lpBrowseInfo As BROWSEINFO) _
As Long

Private Const BIF_RETURNONLYFSDIRS = &H1
Public Function BrowseFolder(szDialogTitle As String) As String
Dim X As Long, bi As BROWSEINFO, dwIList As Long
Dim szPath As String, wPos As Integer

With bi
.hOwner = hWndAccessApp
.lpszTitle = szDialogTitle
.ulFlags = BIF_RETURNONLYFSDIRS
End With

dwIList = SHBrowseForFolder(bi)
szPath = Space$(512)
X = SHGetPathFromIDList(ByVal dwIList, ByVal szPath)

If X Then
wPos = InStr(szPath, Chr(0))
BrowseFolder = Left$(szPath, wPos – 1)
Else
BrowseFolder = vbNullString
End If
End Function

Sub dd()
Dim Str As String

Str = BrowseFolder(“c:\\”)

Debug.Print Str

End Sub

代码来自 http://access.mvps.org/access/api/api0002.htm

opendialog

Outlook VBA 将文件多个邮件的内容保存在一个 TXT 文件中

将Outlook中多个邮件的内容保存在一个 TXT 文件中。运行后需要手工输入一个文件名。

Sub SaveBodytoText()
Dim objItem As Object
Dim strFile As String

‘Exit if you don’t choose any files
If ActiveExplorer.Selection.Count = 0 Then Exit Sub

strFile = InputBox(“Please enter the full path and file name for the merged text:”, “Enter File Name”)

‘write content to a file
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.CreateTextFile(strFile, ForAppending, True)

‘write all the chosen file to a file
For Each objItem In ActiveExplorer.Selection
ts.Write (objItem.Body)
Next

MsgBox “Email text extraction completed!”, vbOKOnly + vbInformation, “DONE!”

ts.Close
Set objItem = Nothing

End Sub

此外比较有用的属性还有下面几个:

ReceivedTime 接收时间
SenderName 发件人姓名
Subject 主题
To 收件人(特别注意,有些Outlook邮件没有收件人,也就是没有这个项!)

如何编译DOS版本的Memtest86+

之前研究过Memtest86+的编译,但是没有搞清楚为什么我自己编译出来的DOS可执行程序版本的始终无法运行,与之相对,编译出来的ISO版本或者软盘版都是正常的。经过一番研究,终于找到了问题的原因。在编译之前,需要修改mt86+_loader.asm中的一个定义:

%define fullsize (164504 + buffer – exeh)
; 164504 is the size of memtest86+ V4.20, adjust as needed!

其中这个164504 应该是你编译一次之后memtest.bin文件的大小。这个文件的大小会跟着你使用的gcc编译器版本不同或者其他原因有着不同的大小(万幸,每次编译结果大小是不变的)。在我的环境下,编译出来的memtest.bin大小为176760bytes,因此这个值需要重新修改。

简单解释,这个值是最终生成DOS EXE的文件头,它决定 Load到内存中的有用的文件代码的大小,当这个值偏小(实际有用的代码要更多,装入的少),会导致不可预料的后果。这就是为什么之前编译出来的并不稳定的原因。

顺便说两句

1. 不要尝试在Windows 7 的 XP Mode中安装Ubuntu 12.04,安装过程中会提示无法找到硬盘。推荐使用VirtualBox作为编译环境以及测试的虚拟机。在它上面能够正常运行的话,
在实体机上也不会有问题。
2. 特别注意,修改上述文件之后最好比较一下是否完全编译进入EXE中,最简单的方式是比较新生成的EXE和之前的EXE文件前面310h字节是否相同
3. Ubuntu 12.04没有内置Nasm,安装方法可以参考这篇 insnasm 文章,来自 Linux公社
4. 此外,还提供了一个包含我编译之后的exe和原始mt420.exe的iso镜像以供比较 mtlabz

dosimg

English Version

A. Source code is downloaded from
http://www.memtest.org/
or you can download a 4.20 here memtest86+-4.20.tar 

B. Compiling environment
Ubuntu 12.04 can be downloaded from http://forum.ubuntu.org.cn/viewtopic.php?f=49&t=349550
Nasm can be downloaded from http://www.nasm.us/pub/nasm/releasebuilds/

C. Virtual machine: I use Virtual Box this time. And I have tested the ‘XP mode’ of Windows 7 it fails. I can install the Ubuntu to it. The Ubuntu 12.04 can’t find the HD on it(I use 12.04. And I’m not sure if other version works well). So please don’t’ use XP mode. It will drive you mad.

D. Install the Ubuntu in your Virtual Box. And you should install a Nasm. Ubuntu 12.04 doesn’t contain a nasm 🙁

E. Here is the key-point. You should modify the definiation in mt86+_loader.asm:

%define fullsize (164504 + buffer – exeh)
; 164504 is the size of memtest86+ V4.20, adjust as needed!

164504 is the size of memtest.bin. You run ‘make dos’ once and get the size of memtest.bin This value may be different if the gcc version is different. It reads 176760 bytes in my side.

F. At last build the source again with ‘make dos’. You will get a ‘memtest.exe’. It’s the a DOS executable file.

Note: Be sure to recompile mt86+_loader.asm! Compare the exe you get and the original one. The value in it head should be different.

Outlook 2010 VBA Debug.Print 和 DateToString

一.在Outlook中可以用 Debug.Pring + 内容 直接输出运行结果。不过需要用 ctrl+G 打开Immediate Window

二.下面的程序可以用来将一个 Date 类型转换为 String

Sub DateToString()
Dim d As Date
d = Now

Debug.Print (d)

Dim s As String

s = Format([d], “yyyymmddhhnnss”)

Debug.Print (s)
End Sub

format具体用法可以参考 http://msdn.microsoft.com/en-us/library/gg251755.aspx

vbadatetostring

机器码对汇编指令的转换工具

偶然间发现 Win7 64位并不提供debug.exe工具,这给反汇编机器码带来一些困难(特别是公司只容许安装正版软件)。在网上搜索发现一个很好用的反汇编引擎 BeaEngine。利用这个引擎编写了一个简单的命令行工具用来实现机器码到汇编指令的转换。

第一步:给定反汇编默认的地址,这对于跳转指令特别重要.按”u”之后输入地址;如果不需要,直接回车即可

第二步:输入欲编译的机器码,回车后即开始转换为汇编指令

特别情况:

1.有可能出现无法识别的指令

2.0×0087 和 0x87 0x00 这样的机器码是等价的,但是0x87 并不等于 0x0087。

3.默认的反编译指令集是32位的,通过修改源程序可以变成8086或者x64的

This is a utility which can covert machine code to assemble code (disassemble). It is based on a disassemble engine which named BeaEngine.
Both source code and executable files are in the package. You can rebuild it with Delphi10.

step1:input a virtual EIP which will be set as the beginning address of the assemble code

step2:input the machine code in Hex. It will be transferred to assemble code after pressing Enter

Note:

1. Some hex number may not be recognized

2. The number ’00’ is meaningful as the prefix of a Hex. Ex. 0x0087 will be recognized as 0x87 and 0x00. It’s different with 0x87.

3. Default instruction is 32 Bits. It can be switched to 8086 or x64 by modifying and rebuilding the source code.

m2a

Delphi 调用BeaEngine 反汇编的例子 (DLL版)

/////////////////////////////////////////////////////////////////////////
//
// Delphi 调用BeaEngine 反汇编的例子
// 这是使用 DLL 的版本,Release程序的时候需要将 BeaEngine.dll一同发布
//
// By Zoologist
// www.lab-z.com
// 2013-02-21
/////////////////////////////////////////////////////////////////////////
program BeaConsoleTest;

{$APPTYPE CONSOLE}

uses
SysUtils,BeaEngineDelphi32;

procedure DisasmCode;
var
MyDisasm:TDISASM;
i,len:integer;
begin
// ======== Init the TDisasm structure (important !)
FillChar(MyDisasm,sizeof(TDISASM),0);

// ======== Init EIP
MyDisasm.EIP:=Int64(@DisasmCode);
MyDisasm.Archi:=0;
MyDisasm.Options:=NoTabulation + MasmSyntax;

// ======== Loop for Disasm
for i:=1 to 20 do
begin
len:=Disasm(MyDisasm);
Writeln(IntToHex(MyDisasm.EIP,2)+’ ‘+MyDisasm.CompleteInstr);
MyDisasm.EIP:=MyDisasm.EIP+len;
end;
end;

begin
Writeln(‘This is a BeaEngine Test program for delphi.’);
DisasmCode;
Writeln(‘Press Enter to exit…’);
Readln;
end.

特别的需要修改 BeaEngineDelphi32.pas 打开使用 DLL 反编译方式

// ====================================================================
// [+] BranchTaken,BranchNotTaken added in TPREFIXINFO v3.1.0
unit BeaEngineDelphi32;
// ====================================================================
// Default link type is static lib
// comment below line to switch link with DLL
// ====================================================================
{$DEFINE USEDLL}
// ====================================================================
// Copyright 2006-2009, BeatriX
// File coded by BeatriX

编译之后的EXE需要连同 DLL 一起发布才能正常工作。下面是完整的例子

Bea1

Delphi 调用BeaEngine 反汇编的例子 (OBJ加入编译)

/////////////////////////////////////////////////////////////////////////
//
// Delphi 调用BeaEngine 反汇编的例子
// 这是直接和Obj进行链接的例子,Release程序的时候直接发布EXE文件即可
//
// By Zoologist
// www.lab-z.com
// 2013-02-22
/////////////////////////////////////////////////////////////////////////

program BeaConsoleTest;

{$APPTYPE CONSOLE}

uses
SysUtils,BeaEngineDelphi32;

procedure DisasmCode;
var
MyDisasm:TDISASM;
i,len:integer;
begin
// ======== Init the TDisasm structure (important !)
FillChar(MyDisasm,sizeof(TDISASM),0);

// ======== Init EIP
MyDisasm.EIP:=Int64(@DisasmCode);
MyDisasm.Archi:=0;
MyDisasm.Options:=NoTabulation + MasmSyntax;

// ======== Loop for Disasm
for i:=1 to 20 do
begin
len:=Disasm(MyDisasm);
Writeln(IntToHex(MyDisasm.EIP,2)+’ ‘+MyDisasm.CompleteInstr);
MyDisasm.EIP:=MyDisasm.EIP+len;
end;
end;

begin
Writeln(‘This is a BeaEngine Test program for delphi.’);
DisasmCode;
Writeln(‘Press Enter to exit…’);
Readln;
end.

使用原来的 BeaEngineDelphi32.pas 无法编译(我用 Delphi 2006),需要加入一些代码,下面斜体显示

implementation
{$IFNDEF USEDLL}
{$L BeaEngineLib.obj}

function strcmp(dest, src: PAnsiChar): DWORD;cdecl;
begin
Result := SysUtils.CompareStr(dest, src);
end;

function strcpy(dest, src: PAnsiChar): PAnsiChar;cdecl;
begin
Result := SysUtils.StrCopy(dest, src);
end;

更具体的修改,请参考附件。

Bea2