Summary: Boe Prox shows how to give the Windows PowerShell console a glass-like look.
Honorary Scripting Guy, Boe Prox, here today filling in for my good friend, The Scripting Guy.
Have you ever sat at your desk while working in the Windows PowerShell console and thought, “Wouldn’t it be great if this console had a more unique look? Perhaps something like a glassy look?”
Well, if that is the case, then you are in luck! I am going to take you through the process of turning your Windows PowerShell console into a glassy console. It may not improve your scripting, but at least it will make others take a second look at your screen and ask you, "What is that window you are typing in?"
To accomplish this feat, I have to dive into the world of platform invoke (p/invoke) and get a little dirty in the world of Win32 APIs. Fortunately, there is an amazing site (What is PInvoke.net?). It is dedicated to this subject by providing signatures for a lot of these APIs, which we can take advantage of.
As you can see from the previous images, I will be using the dwmextendframeintoclientarea and DwmIsCompositionEnabled functions available in dwmapi.dll. Typically, we would push out some C# code in a Here-String, compile all of that (along with anything else that is required for this to work) by using Add-Type, and then load it into a Windows PowerShell session.
But today will be different because I am going to take an alternate route by using Reflection to load everything into memory vs. compiling to a file. Why am I doing this? Because it provides an alternate way to perform this action.
Let’s get the ball rolling by first creating a module builder that will serve as the foundation for the remainder of the things I will be building:
#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
Here I am building an assembly (all in memory) called AeroAssembly and then building the module.
Up next is a requirement by one of the functions to have a Struct available to support the margins of a window (in this case, the Windows PowerShell console). To create this, I need to use my Module Builder.
#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
Now that I have my Struct created, I can simply create the object like this:
PS C:\> New-Object MARGINS
left right top bottom
—- —– — ——
0 0 0 0
Next I begin to build my type—the PInvoke functions will be used as methods from this. This is similar to when you call methods from a type accelerator, such as [math].
#region DllImport
$TypeBuilder = $ModuleBuilder.DefineType('Aero', 'Public, Class')
The Aero type has not actually been created yet, so attempts to call it by [Aero] will end in failure. The next step is to begin defining the functions and loading them into the type as methods.
#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
Here I use my type to begin defining the methods. In the images that I showed earlier define the expected return type of the function (in this case, it is [void], even though the signature shows INT, so I do not define one).
The images also define the required parameters that this method needs to perform the appropriate action without throwing errors. In this case, I need to provide the INTPTR and the MARGINS Struct that I defined earlier. Because I am using the same .dll for both methods, I will begin constructing the next method by using the same type already defined.
#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
This is the same as what I did before. I am defining the next method (DwmIsCompositionEnabled) and supplying the proper return type. In this case, no parameters are required, so I leave that alone.
With my two methods defined in the type, the next step is to finish creating the type and load it into the Windows PowerShell console.
[void]$TypeBuilder.CreateType()
#endregion DllImport
So did this actually work? Let’s take a look at the type and the available methods:
As expected, not only has the type been created and is working, the methods that I defined are also available with the proper parameters required.
All that is left is to run a little more code to create the MARGINS object and apply it to my Windows PowerShell console:
# 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."
}
The end result is that your Windows PowerShell console will have a more glassy look to it:
I prefer a black background because I feel that it gives the glassiest look of all of the background colors. But if you want to try some of the other colors, by all means give it a shot! Just keep in mind that you need to use Clear-Host to clear the screen to apply the new color. Also ensure that a foreground color is not too similar in color, which can prevent being able to view the text.
To change this back to the normal Windows PowerShell console, simply revert the MARGINS values to 0 and rerun the method:
$margin.top = 0
$margin.left = 0
$margin.right = 0
$margin.bottom = 0
[Aero]::DwmExtendFrameIntoClientArea($hwnd, $margin)
I also wrote a function that does all of this for you, and it is available on the Script Center Repository: Enable Glass Console Theme. You can enable or disable the Aero glass theme:
Set-AeroGlass -Enable
Set-AeroGlass -Disable
That is all there is to working with the Win32 API and using Reflection to give your Windows PowerShell console a glassy theme.
I invite you to follow the Scripting Guy on Twitter and Facebook. If you have any questions, send email to the Scripting Guy at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum.
Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy
0 comments