title: Powershell: Properly coloring Get-Childitem output once and for all
body: <p><strong>Edit:</strong> Original solution at the bottom of this post. For a more up-to-date solution, see the accepted answer, posted by Thraka.</p>
  <p>Colorizing Get-Childitem (<em>dir</em> or <em>ls</em>, in other words) isn't a new idea exactly, but I have not been able to locate any ideal approaches to colorizing output in Powershell.  There are two general approaches for writing color-ls functions:</p>
  <ul>
  <li><p>Intercepting output of Get-Childitem, and re-outputting it as text using Write-Host with the -ForegroundColor parameter.  This approach allows as much granularity as possible, but reduces the output of Get-Childitem to text.  As most powershell users are aware, Get-Childitem does not output text, rather, it outputs objects.  Specifically, a list of FileInfo and DirectoryInfo objects.  This allows a great deal of flexibility in handling Get-Childitem output.</p>
  </li>
  <li><p>Pipe the output of Get-Childitem via Invoke-Expression to Foreach-Object, changing the console foreground color before outputting each object.  Kind of a mouthful, but the better option because it preserves the type of Get-Childitem's output.</p>
  </li>
  </ul>
  <p>Here is an example of the latter approach, provided by <a href="http://tasteofpowershell.blogspot.com/2009/02/get-childitem-dir-results-color-coded.html" rel="noreferrer">Tim Johnson's Powershell Blog</a>.</p>
  <pre><code>function color-ls
  {
      $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase `
            -bor [System.Text.RegularExpressions.RegexOptions]::Compiled)
      $fore = $Host.UI.RawUI.ForegroundColor
      $compressed = New-Object System.Text.RegularExpressions.Regex(
            '\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
      $executable = New-Object System.Text.RegularExpressions.Regex(
            '\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
      $text_files = New-Object System.Text.RegularExpressions.Regex(
            '\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)
  
      Invoke-Expression (&quot;Get-ChildItem $args&quot;) | ForEach-Object {
          if ($_.GetType().Name -eq 'DirectoryInfo') 
          {
              $Host.UI.RawUI.ForegroundColor = 'Magenta'
              echo $_
              $Host.UI.RawUI.ForegroundColor = $fore
          }
          elseif ($compressed.IsMatch($_.Name)) 
          {
              $Host.UI.RawUI.ForegroundColor = 'darkgreen'
              echo $_
              $Host.UI.RawUI.ForegroundColor = $fore
          }
          elseif ($executable.IsMatch($_.Name))
          {
              $Host.UI.RawUI.ForegroundColor = 'Red'
              echo $_
              $Host.UI.RawUI.ForegroundColor = $fore
          }
          elseif ($text_files.IsMatch($_.Name))
          {
              $Host.UI.RawUI.ForegroundColor = 'Yellow'
              echo $_
              $Host.UI.RawUI.ForegroundColor = $fore
          }
          else
          {
              echo $_
          }
      }
  }
  </code></pre>
  <p>This code assigns different colors based purely on file extension, but nearly any metric could be substituted to differentiate file types.  The above code produces the following output:</p>
  <p><img src="https://i.stack.imgur.com/v3NeB.png" alt="Colored get-childitem example" /></p>
  <p>This is <em>nearly</em> perfect, but there is one little flaw: the first 3 lines output (Directory path, Column Headers, and horizontal separators) take on the color of the first item in the list.  Tim Johnson commented in his blog:</p>
  <blockquote>
  <p>I would rather if the header at the top wasn't always the same color as the first item, but I can't think of any way around that.</p>
  </blockquote>
  <p>Neither can I, unfortunately.  That's where Stack Overflow and its powershell gurus come in: I'm looking for a way to colorize Get-Childitem output while preserving the cmdlet's output type, without messing up the color of the header.  I've done some experimentation and fiddling with this approach, but have not had any success just yet, as the first single echo call outputs the entire header and first item.</p>
  <p>Any questions, comments, or, even better, solutions are welcome.</p>
  <p><strong>The Solution</strong> With Thanks to jon Z and the others who provided ideas:</p>
  <p>Jon Z provided the perfect solution to this problem, which I have polished up a bit to match the scheme in my original question.  Here it is, for anyone who is interested.  Note that this requires the <a href="http://poshcode.org/2197" rel="noreferrer">New-CommandWrapper cmdlet</a> from the Powershell Cookbook. <strong>Relevant cmdlet added to bottom of this post</strong>.  All of this code goes in your profile.</p>
  <pre><code>function Write-Color-LS
      {
          param ([string]$color = &quot;white&quot;, $file)
          Write-host (&quot;{0,-7} {1,25} {2,10} {3}&quot; -f $file.mode, ([String]::Format(&quot;{0,10}  {1,8}&quot;, $file.LastWriteTime.ToString(&quot;d&quot;), $file.LastWriteTime.ToString(&quot;t&quot;))), $file.length, $file.name) -foregroundcolor $color 
      }
  
  New-CommandWrapper Out-Default -Process {
      $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
  
       
      $compressed = New-Object System.Text.RegularExpressions.Regex(
          '\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
      $executable = New-Object System.Text.RegularExpressions.Regex(
          '\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
      $text_files = New-Object System.Text.RegularExpressions.Regex(
          '\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)
  
      if(($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]))
      {
          if(-not ($notfirst)) 
          {
             Write-Host
             Write-Host &quot;    Directory: &quot; -noNewLine
             Write-Host &quot; $(pwd)`n&quot; -foregroundcolor &quot;Magenta&quot;           
             Write-Host &quot;Mode                LastWriteTime     Length Name&quot;
             Write-Host &quot;----                -------------     ------ ----&quot;
             $notfirst=$true
          }
  
          if ($_ -is [System.IO.DirectoryInfo]) 
          {
              Write-Color-LS &quot;Magenta&quot; $_                
          }
          elseif ($compressed.IsMatch($_.Name))
          {
              Write-Color-LS &quot;DarkGreen&quot; $_
          }
          elseif ($executable.IsMatch($_.Name))
          {
              Write-Color-LS &quot;Red&quot; $_
          }
          elseif ($text_files.IsMatch($_.Name))
          {
              Write-Color-LS &quot;Yellow&quot; $_
          }
          else
          {
              Write-Color-LS &quot;White&quot; $_
          }
          
      $_ = $null
      }
  } -end {
      write-host &quot;&quot;
  }
  </code></pre>
  <p>This produces output that looks like the following screenshot:
  <img src="https://i.stack.imgur.com/dBgm1.png" alt="enter image description here" /></p>
  <p>If you would like the total file size line at the bottom, simply add the following code:</p>
  <pre><code>Remove-Item alias:ls
  Set-Alias ls LS-Padded
  
  function LS-Padded
  {
      param ($dir)
      Get-Childitem $dir
      Write-Host
      getDirSize $dir
  }
  
  function getDirSize
  {
      param ($dir)
      $bytes = 0
  
      Get-Childitem $dir | foreach-object {
  
          if ($_ -is [System.IO.FileInfo])
          {
              $bytes += $_.Length
          }
      }
  
      if ($bytes -ge 1KB -and $bytes -lt 1MB)
      {
          Write-Host (&quot;Total Size: &quot; + [Math]::Round(($bytes / 1KB), 2) + &quot; KB&quot;)   
      }
  
      elseif ($bytes -ge 1MB -and $bytes -lt 1GB)
      {
          Write-Host (&quot;Total Size: &quot; + [Math]::Round(($bytes / 1MB), 2) + &quot; MB&quot;)
      }
  
      elseif ($bytes -ge 1GB)
      {
          Write-Host (&quot;Total Size: &quot; + [Math]::Round(($bytes / 1GB), 2) + &quot; GB&quot;)
      }    
  
      else
      {
          Write-Host (&quot;Total Size: &quot; + $bytes + &quot; bytes&quot;)
      }
  }
  </code></pre>
  <p>As has been pointed out in the comments, the PoshCode New-CommandWrapper link has died. Here is the relevant cmdlet in full:</p>
  <pre><code>##############################################################################
  ##
  ## New-CommandWrapper
  ##
  ## From Windows PowerShell Cookbook (O'Reilly)
  ## by Lee Holmes (http://www.leeholmes.com/guide)
  ##
  ##############################################################################
  
  &lt;#
  
  .SYNOPSIS
  
  Adds parameters and functionality to existing cmdlets and functions.
  
  .EXAMPLE
  
  New-CommandWrapper Get-Process `
        -AddParameter @{
            SortBy = {
                $newPipeline = {
                    __ORIGINAL_COMMAND__ | Sort-Object -Property $SortBy
                }
            }
        }
  
  This example adds a 'SortBy' parameter to Get-Process. It accomplishes
  this by adding a Sort-Object command to the pipeline.
  
  .EXAMPLE
  
  $parameterAttributes = @'
            [Parameter(Mandatory = $true)]
            [ValidateRange(50,75)]
            [Int]
  '@
  
  New-CommandWrapper Clear-Host `
        -AddParameter @{
            @{
                Name = 'MyMandatoryInt';
                Attributes = $parameterAttributes
            } = {
                Write-Host $MyMandatoryInt
                Read-Host &quot;Press ENTER&quot;
           }
        }
  
  This example adds a new mandatory 'MyMandatoryInt' parameter to
  Clear-Host. This parameter is also validated to fall within the range
  of 50 to 75. It doesn't alter the pipeline, but does display some
  information on the screen before processing the original pipeline.
  
  #&gt;
  
  param(
      ## The name of the command to extend
      [Parameter(Mandatory = $true)]
      $Name,
  
      ## Script to invoke before the command begins
      [ScriptBlock] $Begin,
  
      ## Script to invoke for each input element
      [ScriptBlock] $Process,
  
      ## Script to invoke at the end of the command
      [ScriptBlock] $End,
  
      ## Parameters to add, and their functionality.
      ##
      ## The Key of the hashtable can be either a simple parameter name,
      ## or a more advanced parameter description.
      ##
      ## If you want to add additional parameter validation (such as a
      ## parameter type,) then the key can itself be a hashtable with the keys
      ## 'Name' and 'Attributes'. 'Attributes' is the text you would use when
      ## defining this parameter as part of a function.
      ##
      ## The Value of each hashtable entry is a scriptblock to invoke
      ## when this parameter is selected. To customize the pipeline,
      ## assign a new scriptblock to the $newPipeline variable. Use the
      ## special text, __ORIGINAL_COMMAND__, to represent the original
      ## command. The $targetParameters variable represents a hashtable
      ## containing the parameters that will be passed to the original
      ## command.
      [HashTable] $AddParameter
  )
  
  Set-StrictMode -Version Latest
  
  ## Store the target command we are wrapping, and its command type
  $target = $Name
  $commandType = &quot;Cmdlet&quot;
  
  ## If a function already exists with this name (perhaps it's already been
  ## wrapped,) rename the other function and chain to its new name.
  if(Test-Path function:\$Name)
  {
      $target = &quot;$Name&quot; + &quot;-&quot; + [Guid]::NewGuid().ToString().Replace(&quot;-&quot;,&quot;&quot;)
      Rename-Item function:\GLOBAL:$Name GLOBAL:$target
      $commandType = &quot;Function&quot;
  }
  
  ## The template we use for generating a command proxy
  $proxy = @'
  
  __CMDLET_BINDING_ATTRIBUTE__
  param(
  __PARAMETERS__
  )
  begin
  {
      try {
          __CUSTOM_BEGIN__
  
          ## Access the REAL Foreach-Object command, so that command
          ## wrappers do not interfere with this script
          $foreachObject = $executionContext.InvokeCommand.GetCmdlet(
              &quot;Microsoft.PowerShell.Core\Foreach-Object&quot;)
  
          $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(
              '__COMMAND_NAME__',
              [System.Management.Automation.CommandTypes]::__COMMAND_TYPE__)
  
          ## TargetParameters represents the hashtable of parameters that
          ## we will pass along to the wrapped command
          $targetParameters = @{}
          $PSBoundParameters.GetEnumerator() |
              &amp; $foreachObject {
                  if($command.Parameters.ContainsKey($_.Key))
                  {
                      $targetParameters.Add($_.Key, $_.Value)
                  }
              }
  
          ## finalPipeline represents the pipeline we wil ultimately run
          $newPipeline = { &amp; $wrappedCmd @targetParameters }
          $finalPipeline = $newPipeline.ToString()
  
          __CUSTOM_PARAMETER_PROCESSING__
  
          $steppablePipeline = [ScriptBlock]::Create(
              $finalPipeline).GetSteppablePipeline()
          $steppablePipeline.Begin($PSCmdlet)
      } catch {
          throw
      }
  }
  
  process
  {
      try {
          __CUSTOM_PROCESS__
          $steppablePipeline.Process($_)
      } catch {
          throw
      }
  }
  
  end
  {
      try {
          __CUSTOM_END__
          $steppablePipeline.End()
      } catch {
          throw
      }
  }
  
  dynamicparam
  {
      ## Access the REAL Get-Command, Foreach-Object, and Where-Object
      ## commands, so that command wrappers do not interfere with this script
      $getCommand = $executionContext.InvokeCommand.GetCmdlet(
          &quot;Microsoft.PowerShell.Core\Get-Command&quot;)
      $foreachObject = $executionContext.InvokeCommand.GetCmdlet(
          &quot;Microsoft.PowerShell.Core\Foreach-Object&quot;)
      $whereObject = $executionContext.InvokeCommand.GetCmdlet(
          &quot;Microsoft.PowerShell.Core\Where-Object&quot;)
  
      ## Find the parameters of the original command, and remove everything
      ## else from the bound parameter list so we hide parameters the wrapped
      ## command does not recognize.
      $command = &amp; $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__
      $targetParameters = @{}
      $PSBoundParameters.GetEnumerator() |
          &amp; $foreachObject {
              if($command.Parameters.ContainsKey($_.Key))
              {
                  $targetParameters.Add($_.Key, $_.Value)
              }
          }
  
      ## Get the argumment list as it would be passed to the target command
      $argList = @($targetParameters.GetEnumerator() |
          Foreach-Object { &quot;-$($_.Key)&quot;; $_.Value })
  
      ## Get the dynamic parameters of the wrapped command, based on the
      ## arguments to this command
      $command = $null
      try
      {
          $command = &amp; $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__ `
              -ArgumentList $argList
      }
      catch
      {
  
      }
  
      $dynamicParams = @($command.Parameters.GetEnumerator() |
          &amp; $whereObject { $_.Value.IsDynamic })
  
      ## For each of the dynamic parameters, add them to the dynamic
      ## parameters that we return.
      if ($dynamicParams.Length -gt 0)
      {
          $paramDictionary = `
              New-Object Management.Automation.RuntimeDefinedParameterDictionary
          foreach ($param in $dynamicParams)
          {
              $param = $param.Value
              $arguments = $param.Name, $param.ParameterType, $param.Attributes
              $newParameter = `
                  New-Object Management.Automation.RuntimeDefinedParameter `
                  $arguments
              $paramDictionary.Add($param.Name, $newParameter)
          }
          return $paramDictionary
      }
  }
  
  &lt;#
  
  .ForwardHelpTargetName __COMMAND_NAME__
  .ForwardHelpCategory __COMMAND_TYPE__
  
  #&gt;
  
  '@
  
  ## Get the information about the original command
  $originalCommand = Get-Command $target
  $metaData = New-Object System.Management.Automation.CommandMetaData `
      $originalCommand
  $proxyCommandType = [System.Management.Automation.ProxyCommand]
  
  ## Generate the cmdlet binding attribute, and replace information
  ## about the target
  $proxy = $proxy.Replace(&quot;__CMDLET_BINDING_ATTRIBUTE__&quot;,
      $proxyCommandType::GetCmdletBindingAttribute($metaData))
  $proxy = $proxy.Replace(&quot;__COMMAND_NAME__&quot;, $target)
  $proxy = $proxy.Replace(&quot;__COMMAND_TYPE__&quot;, $commandType)
  
  ## Stores new text we'll be putting in the param() block
  $newParamBlockCode = &quot;&quot;
  
  ## Stores new text we'll be putting in the begin block
  ## (mostly due to parameter processing)
  $beginAdditions = &quot;&quot;
  
  ## If the user wants to add a parameter
  $currentParameter = $originalCommand.Parameters.Count
  if($AddParameter)
  {
      foreach($parameter in $AddParameter.Keys)
      {
          ## Get the code associated with this parameter
          $parameterCode = $AddParameter[$parameter]
  
          ## If it's an advanced parameter declaration, the hashtable
          ## holds the validation and / or type restrictions
          if($parameter -is [Hashtable])
          {
              ## Add their attributes and other information to
              ## the variable holding the parameter block additions
              if($currentParameter -gt 0)
              {
                  $newParamBlockCode += &quot;,&quot;
              }
  
              $newParamBlockCode += &quot;`n`n    &quot; +
                  $parameter.Attributes + &quot;`n&quot; +
                  '    $' + $parameter.Name
  
              $parameter = $parameter.Name
          }
          else
          {
              ## If this is a simple parameter name, add it to the list of
              ## parameters. The proxy generation APIs will take care of
              ## adding it to the param() block.
              $newParameter =
                  New-Object System.Management.Automation.ParameterMetadata `
                      $parameter
              $metaData.Parameters.Add($parameter, $newParameter)
          }
  
          $parameterCode = $parameterCode.ToString()
  
          ## Create the template code that invokes their parameter code if
          ## the parameter is selected.
          $templateCode = @&quot;
  
          if(`$PSBoundParameters['$parameter'])
          {
              $parameterCode
  
              ## Replace the __ORIGINAL_COMMAND__ tag with the code
              ## that represents the original command
              `$alteredPipeline = `$newPipeline.ToString()
              `$finalPipeline = `$alteredPipeline.Replace(
                  '__ORIGINAL_COMMAND__', `$finalPipeline)
          }
  &quot;@
  
          ## Add the template code to the list of changes we're making
          ## to the begin() section.
          $beginAdditions += $templateCode
          $currentParameter++
      }
  }
  
  ## Generate the param() block
  $parameters = $proxyCommandType::GetParamBlock($metaData)
  if($newParamBlockCode) { $parameters += $newParamBlockCode }
  $proxy = $proxy.Replace('__PARAMETERS__', $parameters)
  
  ## Update the begin, process, and end sections
  $proxy = $proxy.Replace('__CUSTOM_BEGIN__', $Begin)
  $proxy = $proxy.Replace('__CUSTOM_PARAMETER_PROCESSING__', $beginAdditions)
  $proxy = $proxy.Replace('__CUSTOM_PROCESS__', $Process)
  $proxy = $proxy.Replace('__CUSTOM_END__', $End)
  
  ## Save the function wrapper
  Write-Verbose $proxy
  Set-Content function:\GLOBAL:$NAME $proxy
  
  ## If we were wrapping a cmdlet, hide it so that it doesn't conflict with
  ## Get-Help and Get-Command
  if($commandType -eq &quot;Cmdlet&quot;)
  {
      $originalCommand.Visibility = &quot;Private&quot;
  }
  </code></pre>