有没有那么一天你坐在电脑桌面旁边用着这个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
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!