Set your Powershell console window icon (at runtime)

In my last post I mentioned that I use the title of a console window to help track what codebase/component I’m working with. Another way we organize some of these windows is via the window icon (we happen to have color names for our various codebases, so it works pretty well). Today my co-worker Scott mentioned that if there was a way to programmatically set the icon of the console window, he’d think about switching to Powershell. We normally use different shortcuts to assign a custom icon. I couldn’t pass up the challenge!

Having the .NET framework at your disposal in Powershell is amazing. Add to that the ability to do P/Invoke calls and, while it’s a little more advanced than an average administrator will use, your scripts are really quite unlimited. Thanks to Lee Holmes for the Invoke-Win32 function.

Here is Set-ConsoleIcon.ps1 for your enjoyment! (You can download it here.)

   1:  ##############################################################################
   2:  ##
   3:  ## Script: Set-ConsoleIcon.ps1
   4:  ## By: Aaron Lerch
   5:  ## Website: www.aaronlerch.com/blog
   6:  ##
   7:  ## Set the icon of the current console window to the specified icon
   8:  ##
   9:  ## Usage:  Set-ConsoleIcon [string]
  10:  ##
  11:  ## ie:
  12:  ##
  13:  ## PS:1 > Set-ConsoleIcon "C:Iconsspecial_powershell_icon.ico"
  14:  ##
  15:  ##############################################################################
  16:  
  17:  param(
  18:      [string] $iconFile
  19:  )
  20:  
  21:  $WM_SETICON = 0x80
  22:  $ICON_SMALL = 0
  23:  
  24:  function Main
  25:  {
  26:      [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null
  27:  
  28:      # Verify the file exists
  29:      if ([System.IO.File]::Exists($iconFile) -eq $TRUE)
  30:      {
  31:          $icon = new-object System.Drawing.Icon($iconFile)
  32:  
  33:          if ($icon -ne $null)
  34:          {
  35:              $consoleHandle = GetConsoleWindow
  36:              SendMessage $consoleHandle $WM_SETICON $ICON_SMALL $icon.Handle | out-null
  37:          }
  38:      }
  39:      else
  40:      {
  41:          Write-Host "Icon file not found"
  42:      }
  43:  }
  44:  
  45:  
  46:  ## Invoke a Win32 P/Invoke call.
  47:  ## From: Lee Holmes
  48:  ## http://www.leeholmes.com/blog/GetTheOwnerOfAProcessInPowerShellPInvokeAndRefOutParameters.aspx
  49:  function Invoke-Win32([string] $dllName, [Type] $returnType,
  50:     [string] $methodName, [Type[]] $parameterTypes, [Object[]] $parameters)
  51:  {
  52:     ## Begin to build the dynamic assembly
  53:     $domain = [AppDomain]::CurrentDomain
  54:     $name = New-Object Reflection.AssemblyName 'PInvokeAssembly'
  55:     $assembly = $domain.DefineDynamicAssembly($name, 'Run')
  56:     $module = $assembly.DefineDynamicModule('PInvokeModule')
  57:     $type = $module.DefineType('PInvokeType', "Public,BeforeFieldInit")
  58:  
  59:     ## Go through all of the parameters passed to us.  As we do this,
  60:     ## we clone the user's inputs into another array that we will use for
  61:     ## the P/Invoke call.  
  62:     $inputParameters = @()
  63:     $refParameters = @()
  64:  
  65:     for($counter = 1; $counter -le $parameterTypes.Length; $counter++)
  66:     {
  67:        ## If an item is a PSReference, then the user 
  68:        ## wants an [out] parameter.
  69:        if($parameterTypes[$counter - 1] -eq [Ref])
  70:        {
  71:           ## Remember which parameters are used for [Out] parameters
  72:           $refParameters += $counter
  73:  
  74:           ## On the cloned array, we replace the PSReference type with the 
  75:           ## .Net reference type that represents the value of the PSReference, 
  76:           ## and the value with the value held by the PSReference.
  77:           $parameterTypes[$counter - 1] =
  78:              $parameters[$counter - 1].Value.GetType().MakeByRefType()
  79:           $inputParameters += $parameters[$counter - 1].Value
  80:        }
  81:        else
  82:        {
  83:           ## Otherwise, just add their actual parameter to the
  84:           ## input array.
  85:           $inputParameters += $parameters[$counter - 1]
  86:        }
  87:     }
  88:  
  89:     ## Define the actual P/Invoke method, adding the [Out]
  90:     ## attribute for any parameters that were originally [Ref] 
  91:     ## parameters.
  92:     $method = $type.DefineMethod($methodName, 'Public,HideBySig,Static,PinvokeImpl',
  93:        $returnType, $parameterTypes)
  94:     foreach($refParameter in $refParameters)
  95:     {
  96:        $method.DefineParameter($refParameter, "Out", $null)
  97:     }
  98:  
  99:     ## Apply the P/Invoke constructor
 100:     $ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([string])
 101:     $attr = New-Object Reflection.Emit.CustomAttributeBuilder $ctor, $dllName
 102:     $method.SetCustomAttribute($attr)
 103:  
 104:     ## Create the temporary type, and invoke the method.
 105:     $realType = $type.CreateType()
 106:     $realType.InvokeMember($methodName, 'Public,Static,InvokeMethod', $null, $null,
 107:        $inputParameters)
 108:  
 109:     ## Finally, go through all of the reference parameters, and update the
 110:     ## values of the PSReference objects that the user passed in.
 111:     foreach($refParameter in $refParameters)
 112:     {
 113:        $parameters[$refParameter - 1].Value = $inputParameters[$refParameter - 1]
 114:     }
 115:  }
 116:  
 117:  function SendMessage([IntPtr] $hWnd, [Int32] $message, [Int32] $wParam, [Int32] $lParam)
 118:  {
 119:      $parameterTypes = [IntPtr], [Int32], [Int32], [Int32]
 120:      $parameters = $hWnd, $message, $wParam, $lParam
 121:  
 122:      Invoke-Win32 "user32.dll" ([Int32]) "SendMessage" $parameterTypes $parameters
 123:  }
 124:  
 125:  function GetConsoleWindow()
 126:  {
 127:      Invoke-Win32 "kernel32" ([IntPtr]) "GetConsoleWindow"
 128:  }
 129:  
 130:  . Main

This entry was posted on Friday, February 16th, 2007 at 10:22 pm and is filed under powershell. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

3 Responses to “Set your Powershell console window icon (at runtime)”

  1. Scott Says:

    Guess I have to use Powershell now.

  2. aaron Says:

    Done – I had forgotten about that site, thanks!

  3. Anonymous Says:

    This looks like a script that a lot of people would find useful.

    You could really help the community by submitting it to the ScriptCenter script repository. That is the first place a lot of people will go to when looking for a script to do something.
    You can submit it by using the links at: http://www.microsoft.com/technet/scriptcenter/csc/default.mspx

    Thanks!

    Jeffrey Snover [MSFT]
    Windows PowerShell/MMC Architect
    Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
    Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx