162011
 

在64位Windows中运行32位应用程序的时候,Windows会使用WoW(Windows on Windows)模式来运行它。简单来说,WoW模式是一个运行在64位Windows中的32位Windows的虚拟机,对于32位应用程序来说,它仍然会以为自己运行在32位的Windows当中,它所加载的系统文件、所访问的注册表,也都是32位的版本。不过在64位Windows中,提供了一种方法,可以让32位的应用程序,访问64位的注册表。

.Net Framework 4及以后版本

在.Net 4中,我们可以使用RegistryView枚举来指定打开的64位的注册表,还是32位的注册表。比如下面的C#代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string value64 = string.Empty;
string value32 = string.Empty;

RegistryKey localKey = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry64);
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
if (localKey != null) {
    value64 = localKey.GetValue("RegisteredOrganization").ToString();
}

RegistryKey localKey32 = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry32);
localKey32 = localKey32.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
if (localKey32 != null) {
    value32 = localKey32.GetValue("RegisteredOrganization").ToString();
}

不难发现,当OpenBaseKey的时候,传入RegistryView.Registry64即可以访问64位的注册表。需要注意的是,在纯32位的Windows中,由于没有64位注册表,这个参数将不起作用,最终打开的还是32位注册表。由于RegistryView和OpenBaseKey是.Net中的新增API,所以C#、VB.Net、PowerShell都可以用这种方式来访问。

Windows API

在Windows API中,可以通过调用RegOpenKeyEx并传入KEY_WOW64_64KEY来达到相同的效果。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public enum RegSAM {
    QueryValue = 0x0001,
    SetValue = 0x0002,
    CreateSubKey = 0x0004,
    EnumerateSubKeys = 0x0008,
    Notify = 0x0010,
    CreateLink = 0x0020,
    WOW64_32Key = 0x0200,
    WOW64_64Key = 0x0100,
    WOW64_Res = 0x0300,
    Read = 0x00020019,
    Write = 0x00020006,
    Execute = 0x00020019,
    AllAccess = 0x000f003f
}

public static class RegHive {
    public static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);
    public static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
}

public static class RegistryWOW6432 {

    [DllImport("Advapi32.dll")]
    static extern uint RegOpenKeyEx(
        UIntPtr hKey,
        string lpSubKey,
        uint ulOptions,
        int samDesired,
        out int phkResult);

    [DllImport("Advapi32.dll")]
    static extern uint RegCloseKey(int hKey);

    [DllImport("advapi32.dll", EntryPoint = "RegQueryValueEx")]
    public static extern int RegQueryValueEx(
        int hKey, string lpValueName,
        int lpReserved,
        ref uint lpType,
        System.Text.StringBuilder lpData,
        ref uint lpcbData);

    static public string GetRegKey64(UIntPtr inHive, String inKeyName, String inPropertyName) {
        return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_64Key, inPropertyName);
    }

    static public string GetRegKey32(UIntPtr inHive, String inKeyName, String inPropertyName) {
        return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_32Key, inPropertyName);
    }

    static public string GetRegKey64(UIntPtr inHive, String inKeyName, RegSAM in32or64key, String inPropertyName) {
        //UIntPtr HKEY_LOCAL_MACHINE = (UIntPtr)0x80000002;
        int hkey = 0;

        try {
            uint lResult = RegOpenKeyEx(RegHive.HKEY_LOCAL_MACHINE, inKeyName, 0, (int)RegSAM.QueryValue | (int)in32or64key, out hkey);
            if (0 != lResult) return null;
            uint lpType = 0;
            uint lpcbData = 1024;
            StringBuilder AgeBuffer = new StringBuilder(1024);
            RegQueryValueEx(hkey, inPropertyName, 0, ref lpType, AgeBuffer, ref lpcbData);
            string Age = AgeBuffer.ToString();
            return Age;
        }
        finally
        {
            if (0 != hkey) RegCloseKey(hkey);
        }
    }
}
1
2
string value64 = RegistryWOW6432.GetRegKey64(RegHive.HKEY_LOCAL_MACHINE, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "RegisteredOrganization");
string value32 = RegistryWOW6432.GetRegKey32(RegHive.HKEY_LOCAL_MACHINE, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "RegisteredOrganization");

上述代码是在C#中通过P/Invoke来调用Windows API,适用于.Net 3.5及之前版本,C++中的调用方法类似。

Windows Management Instrumentation (WMI)

WMI是一种标准化的Windows资源的访问方式,使用WMI也可以访问注册表。可以使用__ProviderArchitecture参数指定所需要的平台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
strComputer = "."
Const HKLM = &h80000002
Set objCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
objCtx.Add "__ProviderArchitecture", 32
objCtx.Add "__RequiredArchitecture", TRUE
Set objLocator = CreateObject("Wbemscripting.SWbemLocator")
Set objServices = objLocator.ConnectServer("","root\default","","",,,,objCtx)
Set objStdRegProv = objServices.Get("StdRegProv")

' Use ExecMethod to call the GetStringValue method
Set Inparams = objStdRegProv.Methods_("GetStringValue").Inparameters
Inparams.Hdefkey = HKLM
Inparams.Ssubkeyname = "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
Inparams.Svaluename = "RegisteredOrganization"
set Outparams = objStdRegProv.ExecMethod_("GetStringValue", Inparams,,objCtx)

'Show output parameters object and the registry value HKLM\SOFTWARE\
WScript.Echo Outparams.GetObjectText_
WScript.Echo "WMI Logging is set to  " & Outparams.SValue

WMI可以被多数语言使用,如VBScript、C++,C#也是可以的。

于是,几乎使用任何语言写出来的32位应用程序,都可以在64位Windows上访问64位的注册表了。

022011
 

在某些知识库文章(如KB2419635)中,可以看到wmsjro.dll的身影,但是在系统中,我们只能找到msjro.dll,就是没有前缀’w‘的。这是怎么回事呢?

原来msjro.dll是Jet引擎的一部分,Jet引擎只有32位版本,在64位系统中,它只以WoW的形式存在;而在Windows Update的安装包中,为了区别于正常模式的dll,WoW模式的dll都被加了前缀’w',以示区分。在安装到系统中之后,系统会自动重命名回来。wmsjro.dll会被复制为”C:\Program Files (x86)\Common Files\System\ado\msjro.dll”。其它的例子还是,wntdll.dll(见KB956572)或者wkernel32.dll(见KB944340

顺便提一句,想知道Program Files (x86)文件夹的作用,请看这里

232011
 

在很久之前,大概是Win3.x的时候,Windows中的文件名是由不超过8个字符的主文件名,和不超过3个字符的扩展名组成。到了Windows 95的时候,这个长度被扩展成主文件名+扩展名不超过255个字符。为了保证兼容性,Windows提供了一种转换的方式,大意是用’~'和数字还缩短文件名的长度,比如”C:\Program Files”被转换成了”C:\Progra`1″。这种转换使得一些老的应用程序在新的Windows平台上也可以使用,但它多多少少降低了系统的性能(文件索引之类)。

想知道自己的系统有没有开启这个功能,可以通过以下命令查询:

1
fsutil 8dot3name query c:

“c:”表示查询C盘的状态。如果功能被启用,会显示类似如下的信息:

Disable8dot3 的卷状态为 0 (8dot3 名称创建已启用)。

NtfsDisable8dot3NameCreation 的注册表状态为默认值 2 (卷级别设置)。

基于以上的两个设置,在 c: 上启用 8dot3 名称创建。

如果在C盘上关闭这个功能,则可使用以下命令:

1
fsutil 8dot3name set C: 1

1表示禁用,0表示启用。如果不写盘符,则是全局设置,这时可选的有4个值:0(全部启动),1(全部禁用),2(每个盘符单独设置),3(除系统盘外全部禁用)。全局设置也可以通过注册实现:

键:HKLM\SYSTEM\CurrentControlSet\Control\FileSystem

值:NTFSDisable8dot3NameCreation  类型:REG_DWORD

可选的值就是上述的4个之一,在Windows 7(或者Windows Server 2008 R2)中的默认值是2。

 

202011
 

在使用远程共享文件夹的符号链接(Symbolic Link)时,可能会遇到以下错误:

无法遵循符号链接,因为其类型已禁用

在英文系统中对应的是“The symbolic link cannot be followed because its type is disabled.” 简单的解决方案是,运行以下命令:

1
FSUtil Behavior Set SymlinkEvaluation L2L:1 R2R:1 L2R:1 R2L:1

Windows把文件夹(或文件)分为本地和远程的两种,远程就是通过UNC地址访问的文件,通常是文件服务器上的共享文件。于是符号链接就有4种:本地->本地、本地->远程、远程->本地、远程->远程。默认情况下远程->本地、远程->远程这两种链接是被禁止的。以上命令即是把4种链接都开启。

142011
 

其实%WINDIR%\SysNative文件夹是不存在的,它只是64位Windows系统提供的一种重定向机制。

我们已经知道64位Windows通过System32和SysWoW64两件文件夹来区分64位和32位的系统文件,当32位的应用程序尝试访问System32文件夹的时候,系统会自动把它转到SysWoW64文件夹,这样32位应用程序在32位系统和64位系统就都可以运行了,(而不需要为了64位系统而把System32改成SysWoW64)。

这样就出现了一个问题,32位的应用程序怎么访问真正的System32文件夹呢,即存放64位系统文件的文件夹?答案就是通过SysNative文件夹。这个文件夹并不存在,即在资源管理器中找不到,但当32位的应用程序尝试访问这个文件夹时,64位的Windows会把它重定向到真正的System32文件夹,从而提供了一种让32位应用程序访问64位系统文件的方法。具体细节请参考MSDN

072011
 

Windows中删除文件的操作大家都会,对着文件(或文件夹)的图标按“Delete”键嘛,这样被删除的文件(默认)就进了回收站,要么就是按Shift+Delete使文件不经过回收站直接被删除。这样文件是否真的被删除了呢?

答案是否定的。Delete操作只是让你在资源管理器(或我的电脑)中看不到某个文件,但这个文件的内容,还依然保存在硬盘上。

Windows出于性能的考虑,在删除文件的时候,并不会完整地删除文件内容,而是在文件的开头部位标记了一下,说这个文件被删掉了,之后在浏览文件夹的时候,这个文件就不会被显示出来。所以说,即使误删了一些重要文件,不要慌,使用某些文件还原工具就可以找回被删掉的文件。

接下去就是另一个重要的问题了:如何彻底删除一个文件。(想象一下陈老师,后果很严重呀……)基本上有以下几种方法:

  1. 创建一个新文件去覆盖被删掉的文件。由于系统认为之前那个文件已经无效了,新写入的文件有可能被写在之前那个文件所处的位置上,当然这个是看RP的,系统有自己的策略来决定新文件写在哪里。比较安全的方法是把整个分区(盘)填满,这样就能保证删除的文件都被覆盖掉了。
  2. 格式化。Windows提供了2种格式的方法:快速格式化和完整格式化。顾名思义,快速格式化不会真正地删除磁盘上的内容,只是重写了某些标记。而完整格式化是会把整个分区清理掉的。所以如果你需要确保安全的话,请在格式化的时候,不要勾选“快速格式化”的选项。
  3. 使用安全删除工具,如微软提供的SDelete。这些工具通常会在删除文件的同时,在那个文件的位置上写入一个垃圾内容,从而清理掉原来的文件内容。

附:SDelete是一个命令行工具,它的参数是:
sdelete [-p 次数] [-s] [-q] <文件名或文件夹名>
sdelete [-p 次数] [-z|-c] [盘符]

-c 将空闲空间置零(对于优化虚拟磁盘有好处)
-p 次数 复写文件的次数
-s 递归删除子目录
-q 不打印错误信息(安静模式)
-z 清理多余空间

202011
 

今天在微博上看到有人问这个问题。在装好64位的Windows之后,能在系统盘(通常是C盘)下看到这么一个文件夹,它比我们都知道的Program File文件夹多出来个”(x86)“的后缀。那这个带(x86)的文件夹和不带(x86)的有什么区别呢?

简单来说:Program File (x86)存放了一些32位的系统文件。

64位Windows中提供了一种技术,Windows on Windows 64(即WoW64)。它可以使32位的应用程序正常地运行在64位的Windows中,这样用户在从32位到64位过渡的过程中,不会感受到很大的不便。为了能让32位的程序正常运行,64位的Windows中自带了一大部分的32位的系统文件,当32位程序运行的时候,系统会给它虚拟出一个32位的环境,这样32位程序会以为自己运行在32位Windows中。Windows的系统文件主要是存放在%SystemDrive%\Program Files和%Windir%\System32中(即通常的C:\Program Files和C:\Windows\System32)。64位系统中,这两个文件夹存放的是64位的系统文件,为了存放32位的同名系统文件,64位系统中有另外两个文件夹与之对应,%SystemDrive%\Program Files (86)和%Windir%\SysWoW64(即通常的C:\Program Files (86)和C:\Windows\SysWoW64)。

当32位程序需要访问Program Files或者System32中的文件时,系统会自动转向到Program Files (x86)或者SysWoW64中,这样32位的程序就可以正常的在64位Windows中运行了。类似的情况也发生在应用程序安装的时候,64位的程序一般都会被安装到Program Files中,而32位的程序则是装在Program Files (x86)中。

从64位系统的角度来说,WoW64只是提供了兼容32位程序的方法,而系统本身则完全是64位的,所以把Program Files (x86)和SysWoW64这两个文件夹删除,并不会影响系统的正常运行,但是这种做法是非常不推荐的。现在64位应用程序的普及率并不高,很多厂商不愿意在64位方面投入大量精力,比如常用的聊天工具和多媒体播放工具等。在实际使用过程中,很难避免使用32位的应用程序,所以还是把WoW64留着吧。

小提示:32位和64位系统有什么差异?请看这里

152011
 

在每个硬盘分区里都会有一个文件夹叫做“System Volume Information”,而且一般用户还不能访问。它的字面意思是用来存放系统信息的,但是具体存了哪些东西呢?

根据Raymond的解释,它存放了以下信息:

  • 系统还原的数据。系统还原的数据可以在控制面板中删掉。
  • 分布链接跟踪服务的数据,用于个修复快捷方式和文档的链接。
  • 文件索引服务的数据,用于快速查找文件。
  • 卷影服务,主要用于系统文件的备份。
  • Vista系统还用它来保存WinFS的数据库。

如果以上的数据对你来说都没什么用,可以停用上述服务,以减少System Volume Information文件夹的大小。

012009
 

连接到服务器上之后,第一步要做的当然是拿到虚拟机的列表了:

1
2
3
4
5
6
7
8
private ManagementObjectCollection RefreshVirtualMachines()
{
    ManagementScope scope = new ManagementScope(@"\\" + ServerName + @"\root\virtualization");
    scope.Connect();
    ObjectQuery query = new ObjectQuery("SELECT * FROM MsVM_ComputerSystem WHERE Caption LIKE 'Virtual%' ");
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
    return searcher.Get();
}

很简单吧,一个查询就搞定了。得到的ManagementObjectCollection中的每个ManagementObject,对应一个虚拟机的数据。接下来就是生成虚拟机的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class VirtualMachine
{
    internal VirtualMachine(ManagementObject obj)
    {
        BaseObject = obj;
    }

    private ManagementObject BaseObject { get; set; }

    public string Name
    {
        get
        {
            return BaseObject.GetPropertyValue("ElementName").ToString();
        }
    }

    public VirtualMachineState State
    {
        get
        {
            return VirtualMachineState.GetState(BaseObject.GetPropertyValue("EnabledState").ToString());
        }
    }

    public Guid Id
    {
        get
        {
            return new Guid(BaseObject.GetPropertyValue("Name").ToString());
        }
    }

    public TimeSpan UpTime
    {
        get
        {
            return TimeSpan.FromMilliseconds((double)(ulong)BaseObject.GetPropertyValue("OnTimeInMilliseconds"));
        }
    }
}

每个ManagementObject中包含一个虚拟机的数据,详细的列表在这里。不过我只找到4个有用的:Name, ElementName, EnabledState和OnTimeInMilliseconds:

  • Name是一个GUID,它是虚拟机的标识符。
  • ElementName相当于Display Name,也就是用户所看到的名字。
  • EnabledState是虚拟机当前的状态,有很多,详见后面的代码。
  • OnTimeInMilliseconds表示虚拟机开启了多少时间。OnTimeInMilliseconds indicates how long the virtual machine is on.

EnabledState是一个字符串形式的数字,要把它转换成用户看得懂的数据,还需要一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class VirtualMachineState
{
    private VirtualMachineState(ushort id, string name, string displayName)
    {
        Id = id;
        Name = name;
        DisplayName = displayName;
    }

    public string Name { get; private set; }
    public ushort Id { get; private set; }
    public string DisplayName { get; private set; }

    public override string ToString()
    {
        return DisplayName + " (" + Id + ")";
    }

    public static VirtualMachineState GetState(string name)
    {
        ushort id = 0;
        bool useId = UInt16.TryParse(name, out id);
        foreach (VirtualMachineState state in states)
        {
            if ((useId &amp;&amp; state.Id == id) || (state.Name == name))
            {
                return state;
            }
        }
        return Unknown;
    }

    private static List states = new List();
    static VirtualMachineState()
    {
        states.Add(Unknown);
        states.Add(Enabled);
        states.Add(Disabled);
        states.Add(Paused);
        states.Add(Suspended);
        states.Add(Starting);
        states.Add(Sanpshotting);
        states.Add(Saving);
        states.Add(Stopping);
        states.Add(Pausing);
        states.Add(Resuming);
    }

    public static VirtualMachineState Unknown = new VirtualMachineState(0, "Unknown", "Unknown");
    public static VirtualMachineState Enabled = new VirtualMachineState(2, "Enabled", "Running");
    public static VirtualMachineState Disabled = new VirtualMachineState(3, "Diabled", "Off");
    public static VirtualMachineState Paused = new VirtualMachineState(32768, "Paused", "Paused");
    public static VirtualMachineState Suspended = new VirtualMachineState(32769, "Suspended", "Saved");
    public static VirtualMachineState Starting = new VirtualMachineState(32770, "Starting", "Starting");
    public static VirtualMachineState Sanpshotting = new VirtualMachineState(32771, "Snapshotting", "Snapshooting");
    public static VirtualMachineState Saving = new VirtualMachineState(32773, "Saving", "Saving");
    public static VirtualMachineState Stopping = new VirtualMachineState(32774, "Stopping", "Shuting down");
    public static VirtualMachineState Pausing = new VirtualMachineState(32776, "Pausing", "Pausing");
    public static VirtualMachineState Resuming = new VirtualMachineState(32777, "Resuming", "Resuming");
}

用VirtualMachineState.GetState把ManagementObject里的EnableState转化成VirtualMachineState。一共有11种可能的状态,最常见的Enabled和Disabled,即开着和关着。

这样就可以获取服务器上的所有虚拟机的信息,然后显示给用户了。

262009
 

Hyper-VVirtual Server的升级版,主要用于运行和管理虚拟机。它的优点是可以同时管理和维护多台虚拟机,在不需要某台虚拟机的时候,可以把它关掉,用到再开开来,相比于使用物理机器,Hyper-V可以更好的优化资源的利用。利用强大的快照(Snapshot)功能,可以省去重装机器的烦恼。比如在机器刚装好的时候,做一个快照,用了一会觉得系统有问题了,再恢复到之前的状态,就相当于重装了一遍机器了。

Windows Server 2008 (及R2)中提供了一个直观的Hyper-V的管理界面,不仅可以管理本机的虚拟机,也可以连到别的服务器上去。但这毕竟是用户界面,需要手工操作的。在人们越来越懒的时代,我们更希望有自动化的管理,比如每隔一小时刷新一下虚拟机的状态,看看哪台机器不正常,然后发一封邮件出来……生活是多么惬意呀……

接下来开始第一步,连接到远程的Hyper-V服务器。Hyper-V的API是基于WMI的。至于WMI是什么,就不多说了,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static VirtualSystemService Connect(string serverName)
{
    ManagementObject service = null;
    try
    {
        ManagementScope scope = new ManagementScope(@"\\" + serverName + @"\root\virtualization");
        scope.Connect();
        ObjectQuery query = new ObjectQuery("SELECT * FROM MsVM_VirtualSystemManagementService");
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
        ManagementObjectCollection coll = searcher.Get();
        foreach (ManagementObject obj in coll)
        {
            service = obj;
        }
    }
    catch (ManagementException)
    {
        return null;
    }
    if (service == null)
    {
        return null;
    }
    return new VirtualSystemService(service, serverName);
}

第一行中的VirtualSystemService是一个自定义类型,用于对Hyper-V的控制。关键代码是第7行,当目标机器上没有Hyper-V服务的时候,scope.Connect();会抛出一个异常……不多说了,这段代码还是很简单易懂的。