有没有那么一天你坐在电脑桌面旁边用着这个PowerShell蓝底白字控制台,发出哀怨:“你怎么老是这样一成不变,就不能换身马甲?比如换一身玻璃主题嘛?”
果真这样,朋友,你有福了。我将会和你一起把这个控制台转换成玻璃控制台,当然这本身提高不了你的编码能力,但是可以让别人经过你身旁时好奇的问一句:“咦,你刚才打字的这个窗口,是个什么东东?”
为了完成这件壮举,我不能不穿越至平台调用(P/Invoke)的世界,Win32 APIs的世界还是挺乱的。不过很幸运,我发现个神奇网站 (它就是 PInvoke.net?),这网站是干哈的呢,它专门列出一些我们可以调用的API和方法签名。
正如你前两张图片看到的那样,我会使用 dwmapi.dll 中两个函数dwmextendframeintoclientarea 和DwmIsCompositionEnabled 。通常情况下,我们会提出把一些放在 Here-String中 C# 代码,通过Add-Type来编译,仅此而已,然后它就会被加载进PowerShell会话。
但是今天我们不这样做,我会另辟蹊径,通过反射来把所有东西加载进内存,而不是编译成一个文件。为什么我要这样做?因为它提供给了我们另外一条思路。
继续往下走,先创建一个模块生成器,以它为基础,剩下的事也就是我要编译的:
#region Module Builder
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('AeroAssembly')
# Only run in memory
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('AeroModule', $False)
#endregion Module Builder
这里,我创建了一个程序集(在内存中),然后编译这个模块。接下里需要一个能够支持窗口(该场景中,也就是windows PowerShell控制台了)margin的函数。要创建它,须要使用我的模块生成器。
#region STRUCTs
#region Margins
$Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit'
$TypeBuilder = $ModuleBuilder.DefineType('MARGINS', $Attributes, [System.ValueType], 1, 0x10)
[void]$TypeBuilder.DefineField('left', [Int], 'Public')
[void]$TypeBuilder.DefineField('right', [Int], 'Public')
[void]$TypeBuilder.DefineField('top', [Int], 'Public')
[void]$TypeBuilder.DefineField('bottom', [Int], 'Public')
#Create STRUCT Type
[void]$TypeBuilder.CreateType()
#endregion Margins
#endregion STRUCTs
现在我有Structs了,可以这样非常简单的创建对象:
PS C:\> New-Object MARGINS left right top bottom ---- ----- --- ------ 0 0 0 0
接下里我开始生成了我的类型,PInvoke 函数将在这里作为一个方法。这和我们从一个类型accelerator中调用方法是类似的,比如:[math]。
#region DllImport
$TypeBuilder = $ModuleBuilder.DefineType('Aero', 'Public, Class')
Aero类型实际上还没有创建好,所以我们尝试调用 [Aero] 会以失败告终。接下里开始定义函数,并且作为方法把它加载成类型。
#region DwmExtendFrameIntoClientArea Method
$PInvokeMethod = $TypeBuilder.DefineMethod( 'DwmExtendFrameIntoClientArea',
#Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[Void], #Method Return Type
[Type[]] @([IntPtr],[Margins]) #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig')
)
$FieldValueArray = [Object[]] @(
'DwmExtendFrameIntoClientArea',
#CASE SENSITIVE!!
$False
)
$CustomAttributeBuilder = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('dwmapi.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($CustomAttributeBuilder)
#endregion DwmExtendFrameIntoClientArea Method
这里我使用我的类型开始定义方法。在之前的图片中显示了函数期望的返回类型(这里,使用了[void],虽然方法签名显示为INT,但是我没有定义它)。
图片中还定义了这个方法所需的参数,我们也要相应处理,防止它抛出异常。这里,我要提供了INTPTR 和之前定义的MARGINS 结构,因为我会把两个方法都定义在一个dll,所以会使用已经定义好的类型开始构造下一个方法。
#region DwmIsCompositionEnabled Method
$PInvokeMethod = $TypeBuilder.DefineMethod(
'DwmIsCompositionEnabled', #Method Name
[Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
[Bool], #Method Return Type
$Null #Method Parameters
)
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
[Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig')
)
$FieldValueArray = [Object[]] @(
'DwmIsCompositionEnabled', #CASE SENSITIVE!!
$False
)
$CustomAttributeBuilder = New-Object Reflection.Emit.CustomAttributeBuilder(
$DllImportConstructor,
@('dwmapi.dll'),
$FieldArray,
$FieldValueArray
)
$PInvokeMethod.SetCustomAttribute($CustomAttributeBuilder)
#endregion DwmIsCompositionEnabled Method
这和我之前所做的是一样的,我定义了另一个方法(DwmIsCompositionEnabled)并且提供了正确的返回类型,这里不需要参数,所以我没碰它。
借助于定义在该类型中两个方法,下一步只需完成类型创建,并把它加载进Windows PowerShell控制台。
[void]$TypeBuilder.CreateType() #endregion DllImport
这样有用吗?我们看一下这个类型和它支持的方法:
和期望的一样,不旦创建的类型有用,而且定义的方法也包含了所须的参数。最后仅需运行一点代码创建MARGINS 对象,并把它应用到Windows PowerShell控制台:
# Desktop Window Manager (DWM) is always enabled in Windows 8
# Calling DwmIsCompsitionEnabled() only applies if running Vista or Windows 7
If ([Aero]::DwmIsCompositionEnabled()) {
$hwnd = (Get-Process -Id $PID).mainwindowhandle
$margin = New-Object 'MARGINS'
Switch ($PSCmdlet.ParameterSetName) {
'Enable' {
# Negative values create the 'glass' effect
$margin.top = -1
$margin.left = -1
$margin.right = -1
$margin.bottom = -1
$host.ui.RawUI.BackgroundColor = "black"
$host.ui.rawui.Foregroundcolor = "white"
Clear-Host
}
}
[Aero]::DwmExtendFrameIntoClientArea($hwnd, $margin)
} Else {
Write-Warning "Aero is either not available or not enabled on this workstation."
}
最后的结果就是让Windows PowerShell 控制台拥有了玻璃主题:
我更喜欢把控制台背景设置成黑色,这样玻璃主题应用后控制台标题栏和控制台本是颜色融为一体,当然你也可以试着改成其它颜色,不过别忘了改了控制台背景色后要使用clear-host命令来刷新控制台。另外尽量把背景色和前景色设置地不要太相近,这样看文字才会更清楚。
要把控制台改回原来的样子,也很简单,只需把MARGINS设置重置成0即可。
$margin.top = 0 $margin.left = 0 $margin.right = 0 $margin.bottom = 0 [Aero]::DwmExtendFrameIntoClientArea($hwnd, $margin)
当然我把整理后的脚本发布到了脚本中心,你可以在ISE的脚本浏览器中搜索:“Enable Glass Console Theme”。
然后这样调用即可:
. .\Set-Aeroglass.ps1 Set-AeroGlass -Enable Set-AeroGlass -Disable
原文作者:Boe Proxy
原文链接:Give Your PowerShell Console a Glassy Theme
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!




