Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

All Hail Dir UseALot!

Posted on November 16, 2009November 16, 2009

Some of you know my relationship with the a command prompt goes back a long, long way. Naturally I became very adept at using the DIR command, fully taking advantage of its switches to tease out hidden information or to quickly get just the information I wanted. When PowerShell first came out, I made the transition to the DIR alias without too much difficulty. Although I still found myself wanting to run a command like DIR /ad  (ie list only directories). Yes, you can achieve the same results with the PowerShell cmdlets, but that takes too much typing, especially for something I might want to use on a regular basis. I finally got around to writing a DIR function for PowerShell that better emulates my beloved DIR command from the CMD shell.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

The function is called Get-FolderItem, although when I load the function I also create aliases pdir and gfi. This is a PowerShell v2 function complete with help and examples.

captured_Image.png

Here’s the code listing then I’ll explain a few highlights.

Function Get-FolderItem {

#requires -version 2.0

<#

.Synopsis

    A PowerShell version of the DIR command from the CMD shell that supports sorting and filtering.

.Description

    The DIR command from the CMD shell has useful parameters to sort output by date, /od, by size /os and by

    name /on. PowerShell's Get-ChildItem needs to pipe output to other cmdlets to achieve the same results. 

    This function offers a shortcut. This function also supports the equivalent of /b, /a, and /q. You may

    need to pipe the function to other cmdlets like Format-Table or Select-Object to achieve the exact results

    you want.

    This function includes a custom format PS1XML file, psdir.FileSystem.Format.ps1xml, that should be stored

    in your profile folder. When you use the -owner parameter, the custom format file is loaded so that

    owner information is displayed by default. You can also pipe output to Format-Table or Format-List and

    get Owner information. The original formatting file is reloaded before the function ends.

    This function supports -Verbose. Use it to troubleshoot or debug the function.

.Parameter Path

    Enter a folder path. The default is the current directory.

.Parameter Filter

    A file filter like *.txt.  The default *.*

.Parameter Order

    The attribute to sort on. Possible values are d[ate],s[ize],e[xtension] and n[ame], which is the default.

    Prepend -- before the value, ie --date to sort in reverse order. This parameter has an alias of 'o'

.Parameter Attribute

    Get a directory listing based on an attribute such as a[rchive], d[irectory], h[idden], s[system] and

    r[ead]. Equivalent to /A. Use -- before a value to return results NOT matching. For example, to find

    all files without the archive attribute set use -attribute --a. You can also combine attributes, for

    example -attribute dhr to return only read-only and hidden directories. Using this parameter may still

    display hidden and system files. For example, if you search for attribute 'a', any hidden or system

    files with this attribute will also be displayed.

.Parameter Bare

    Display full path only. Equivalent to /B. This parameter cannot be used with any other parameters.

.Parameter Owner

    Adds the owner of each file or folder.

    This function includes a custom format PS1XML file, psdir.FileSystem.Format.ps1xml, that should be stored

    in your $profile folder. When you use the -owner parameter, the custom format file is loaded so that

    owner information is displayed by default. You can also pipe output to Format-Table or Format-List and

    get Owner information. The original formatting file is reloaded before the function ends.

.Parameter Recurse

    Recursively search the path.

.Example

PS C:\> get-folderitem -order date

Display files in the current directory sorted by date.

.Example

PS C:\> get-folderitem c:\scripts *.ps1 -order --size -recurse

Recursively search the scripts the directory for all PowerShell files and display output sorted by size in descending order.

.Example

PS C:\> gfi f:\files -owner -recurse | Sort Owner | Select Owner,FullName,Length,CreationTime,LastWriteTime

Search files under F:\Files and display the full filename, the owner, the file size, when it was created and when

it was last modified. The results are sorted by owner.

.Example

PS C:\> get-folderitem f:\files -recurse -attribute a -bare

Search F:\files for all files with an attribute of A and use a bare file listing.

.Inputs

Accepts strings of folder names

.Outputs

File and Folder objects

.Link

https://jdhitsolutions.com/blog/

.Link

Get-ChildItem

.Notes

NAME:      Get-FolderItem

VERSION:   1.0

AUTHOR:    Jeffery Hicks  https://jdhitsolutions.com/blog

LASTEDIT:  11/16/2009

#>

[CmdletBinding(DefaultParameterSetName="Set1")]

#define the parameters

Param (

  [Parameter(

  ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True,

  Position=0,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Enter a folder path")]

  [Parameter(ParameterSetName="Set2",Position=0)]

  [ValidateScript({

    if ( (Test-Path $_) -AND ((Get-Item $_).PSProvider.Name -eq "FileSystem")) {

       $True

   } else {

      Write-Warning "Can't find $_ or verify that it is a FileSystem PSDrive."

      }

   } )]

  [String[]]$Path=".",

  [Parameter(

  ValueFromPipeline=$False,

  Position=1,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="A file filter like *.txt")] 

  [Parameter(ParameterSetName="Set2", Position=1)] 

  [String]$Filter="*.*",

  [Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Sort order. Possible values are date,size,extension and name, which is the default.

  Prepend a -- sign before the value, ie --date to sort in reverse order.")]

  [Parameter(ParameterSetName="Set2")] 

  [Alias("o")] 

  [ValidateSet("date","size","extension","name","--date","--size","--extension","--name")]

  [string]$Order="name",

  [Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Select a file or folder attribute.")] 

  [Parameter(ParameterSetName="Set2")] 

  [ValidateScript( { foreach ($a in ($_.ToCharArray())) {

     If (! ("-darhs").contains($a)) {

       Write-Warning "Valid attributes are d,a,r,h and s."

     }

     else {$True}

     }

   })]

  [String]$Attribute,

  [Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Recursively search the path.")] 

  [Parameter(ParameterSetName="Set2")] 

  [Switch]$Recurse,

  [Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set2",

  HelpMessage="Select a file or folder attribute.")] 

  [Switch]$Bare,

  [Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Add the file owner as a new property.")] 

  [Switch]$Owner

 )

 #Start Main Function Body

 Begin {

        Write-Verbose "Processing BEGIN Script Block"

        #define the path to the custom format file

        $profileDir=Split-Path $profile

        $formatfile="$profiledir\pdir.FileSystem.format.ps1xml"

        #check for -- at the beginning of the $order parameter

        if ($order.substring(0,2) -match "--" ) {

            Write-Verbose "descending is True"

            $descending=$True

        }

        Write-Verbose "Evaluating $order"       

        #evaluate parameters

        switch -regex ($Order) {

        "name$" {$sort="name";break}

        "date$" {$sort="LastWriteTime";break}

        "size$" {$sort="Length";break}

        "extension$" {$sort="extension";break}

        default {

            #this warning should never be reached but just in case...

            Write-Warning "Invalid value $order for Order parameter. Valid choices are date,name,size and extension. Use --value to sort in reverse order."

            $fail=$True

            return

            }

        } #end switch

        Write-Verbose "Sorting on $sort"

        Write-Verbose "Initializing `$data"

        #initialize results variable

        $data=@()

       Write-Verbose "Exiting BEGIN script block"

} #end Begin

Process {

    #bail if $fail was found, but this should never happen

    if ($fail) {return}

        If ($_) {

            Write-Verbose "Assigning pipelined input to `$Path"

            $Path=$_

        }

      Write-Verbose "Getting files $filter from root of $path"

      #get files from root directory of $path

      $data+=Get-ChildItem -Path $path -Filter $Filter -force

      if ($Recurse) {

         Write-Verbose "Getting files recursively"

         $data+= Get-ChildItem $Path -Recurse -Force | where {$_.PSIsContainer} | foreach {

          Write-Verbose "Processing $($_.fullname)"

          Get-ChildItem $_.FullName -filter $Filter -force

        } #end ForEach

      }

    Write-Verbose "Finished gathering files"

} #end Process

End {

    Write-Verbose "Processing END script block"

    #filter and sort as necessary and write final objects to the pipeline

    #filter by attributes and turn attribute values into a character array

    if ($Attribute -AND $attribute.StartsWith("--")) {

        Write-Verbose "Getting files that match $attribute"

        $modes=$Attribute.Substring(2).ToCharArray()

        #get the first attribute and filter

        Write-Verbose "Filtering for $($modes[0])"

        $data=$data | where {$_.mode -notmatch $modes[0]}

        #filter for each additional mode

        for ($i=1;$i -lt $modes.count;$i++) {

            Write-Verbose "Filtering for $($modes[$i])"

            $data=$data | where {$_.mode -notmatch $modes[$i]}

        }

    }

    elseif ($Attribute) {

       Write-Verbose "Getting files that match $attribute"

       $modes=$Attribute.ToCharArray()

       #get the first attribute and filter

       Write-Verbose "Filtering for $($modes[0])"

       $data=$data | where {$_.mode -match $modes[0]}

        #filter for each additional mode

        for ($i=1;$i -lt $modes.count;$i++) {

            Write-Verbose "Filtering for $($modes[$i])"

            $data=$data | where {$_.mode -match $modes[$i]}

        }

        }

        else {

          Write-Verbose "Removing system and hidden files since no special attributes were passed."

          $data=$data | where {$_.Mode -notmatch "s" -AND $_.mode -notMatch "h"}

        }

       #sort all results PSParentPath to keep everything grouped by folder

      If ($descending) {

          Write-Verbose "Sorting data by $sort in descending order"

          $data = $data | sort -property PSParentPath | sort -property $sort -descending

      }

      Else {

          Write-Verbose "Sorting data by $sort"

          $data= $data  | sort -property PSParentPath,$sort

      }

       #if $bare write out only the full filename

       if ($Bare) {

         Write-Verbose "Writing data in bare mode"

         $data | select FullName

       }

      elseif ($owner) {

        Write-Verbose "Writing data with owner information"

        Write-Verbose "Loading custom format File $formatfile"

        Update-FormatData -prepend $formatfile -ea "SilentlyContinue"

        #add owner information

         $data | foreach {

           $_ | Add-Member -MemberType NoteProperty -Name "Owner" -value ((Get-ACL $_.fullname).Owner ) -passthru

          }

        #reload original FileSystemFormat

        Write-Verbose "Reloading $pshome\filesystem.format.ps1xml"

        Update-FormatData -prepend $pshome\filesystem.format.ps1xml -ea "SilentlyContinue"

      }

      else {

        #just write the results to the pipeline

         Write-Verbose "Write results to the pipeline"

          write $data

         }

     Write-Verbose "Finished"

 } #end END

} #end function

Set-Alias pdir Get-FolderItem

Set-Alias gfi Get-FolderItem

The function is really a wrapper for Get-ChildItem occasionally combined with Where-Object. It takes a file path as the first parameter, defaulting to the current folder if nothing is specified. The parameter includes a validateScript attribute to verify that you are trying to get a directory listing of a FileSystem PSdrive.

Param (

  [Parameter(

  ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True,

  Position=0,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Enter a folder path")]

  [Parameter(ParameterSetName="Set2",Position=0)]

  [ValidateScript({

    if ( (Test-Path $_) -AND ((Get-Item $_).PSProvider.Name -eq "FileSystem")) {

       $True

   } else {

      Write-Warning "Can't find $_ or verify that it is a FileSystem PSDrive."

      }

   } )]

  [String[]]$Path=".",

The parameter can also take pipelined input, although I rarely use it that way.

Unlike the DIR command, I needed to separate out any filtering like *.txt to a separate parameter. It is positional, but only if you specify a path.

PS C:\> pdir . *.txt

Directory: C:\

Mode                LastWriteTime     Length Name

----                -------------     ------ ----

-a---         8/31/2009   7:13 AM         55 computers.txt

-a---        10/14/2009   8:25 AM        406 FolderACLScan.txt

-a---          5/8/2009   8:29 AM          0 nvlog.txt

You’ll notice the output is what you would expect from Get-ChildItem. The function allows you to sort the output by size, name, date and extension. This is similiar to the /o parameter from DIR. To sort in reverse order in CMD you would use /O-d (reverse date order). In my function use – before the value.

PS C:\> pdir –o –date

You can use –o by the way because I defined it as an alias.

[Parameter(

  ValueFromPipeline=$False,

  Mandatory=$False,

  ParameterSetName="Set1",

  HelpMessage="Sort order. Possible values are date,size,extension and name, which is the default. 

  Prepend a -- sign before the value, ie --date to sort in reverse order.")]

  [Parameter(ParameterSetName="Set2")]

  [Alias("o")]

  [ValidateSet("date","size","extension","name","--date","--size","--extension","--name")]

  [string]$Order="name",

There is also a ValidateSet attribute to verify the value passed.

The main part of the function uses Get-ChildItem and saves the results to a variable.

#get files from root directory of $path

    $data+=Get-ChildItem -Path $path -Filter $Filter -force

    if ($Recurse) {

       Write-Verbose "Getting files recursively"

       $data+= Get-ChildItem $Path -Recurse -Force | where {$_.PSIsContainer} | foreach {

        Write-Verbose "Processing $($_.fullname)"

        Get-ChildItem $_.FullName -filter $Filter -force

      } #end ForEach

    }

Once I have the data, then I can apply my filtering and sorting. For example  I  wanted to be able to filter on file attributes like A, H, or S like the /A switch in DIR, including the ability to display files that DON’T match and the option to filter on multiple modes like HS. Because I have no way of knowing what combination of modes might be passed or whether I need to find files that don’t match, I split the attribute parameter value into an array and then find files that match (or don’t match) by iterating through the array.

#filter by attributes and turn attribute values into a character array

   if ($Attribute -AND $attribute.StartsWith("--")) {

       Write-Verbose "Getting files that match $attribute"

       $modes=$Attribute.Substring(2).ToCharArray()

       #get the first attribute and filter

       Write-Verbose "Filtering for $($modes[0])"

       $data=$data | where {$_.mode -notmatch $modes[0]}

       #filter for each additional mode

       for ($i=1;$i -lt $modes.count;$i++) {

           Write-Verbose "Filtering for $($modes[$i])"

           $data=$data | where {$_.mode -notmatch $modes[$i]}

       }

   }

   elseif ($Attribute) {

      Write-Verbose "Getting files that match $attribute"

      $modes=$Attribute.ToCharArray()

      #get the first attribute and filter

      Write-Verbose "Filtering for $($modes[0])"

      $data=$data | where {$_.mode -match $modes[0]}

       #filter for each additional mode

       for ($i=1;$i -lt $modes.count;$i++) {

           Write-Verbose "Filtering for $($modes[$i])"

           $data=$data | where {$_.mode -match $modes[$i]}

       }

The data set is revised each time.  If no attributes were passed then I clean up the data set to remove hidden and system files. This makes the output more like the regular DIR and Get-Childitem commands.

else {

         Write-Verbose "Removing system and hidden files since no special attributes were passed."

         $data=$data | where {$_.Mode -notmatch "s" -AND $_.mode -notMatch "h"}

       }

All that is really left is to sort the results by the parent folder, again to make the output more like Get-ChildItem. If a sort value was passed, then data is sorted by that value.

#sort all results PSParentPath to keep everything grouped by folder

   If ($descending) {

       Write-Verbose "Sorting data by $sort in descending order"

       $data = $data | sort -property PSParentPath | sort -property $sort -descending

   }

   Else {

       Write-Verbose "Sorting data by $sort"

       $data= $data  | sort -property PSParentPath,$sort

   }

At this point I can simply write $data to the pipeline. But the CMD DIR has two other parameters I like to use /bare and /owner.  The former displays the full filename and path and nothing else.

#if $bare write out only the full filename

     if ($Bare) {

       Write-Verbose "Writing data in bare mode"

       $data | select FullName

     }

The latter is only slightly more involved.  I can get the owner information for each file or folder object using Get-ACL and adding a new property with Add-Member.

#add owner information

$data | foreach {

   $_ | Add-Member -MemberType NoteProperty -Name "Owner" -value ((Get-ACL $_.fullname).Owner ) -passthru

}

If I write $data to the pipeline, you woiuldn’t see this information, unless you asked for it:

PS C:\> pdir c:\files –owner | Sort Owner | select Fullname,Length,Owner

But that seemed like a lot of extra work and something you’d have to remember to do. My solution was to create a custom format extension for files and folders that automatically would display the Owner. The file pdir.FileSystem.format.ps1xml is expected to be found in your PowerShell profile directory.

#define the path to the custom format file

$profileDir=Split-Path $profile

$formatfile="$profiledir\pdir.FileSystem.format.ps1xml"

This file will temporarily replace the default formatting by prepending with the custom formatting using Update-FormatData.

elseif ($owner) {

        Write-Verbose "Writing data with owner information"

        Write-Verbose "Loading custom format File $formatfile"

        Update-FormatData -prepend $formatfile -ea "SilentlyContinue"

captured_Image.png[8]

But don’t worry, the function reloads the originally formatting directives.

#reload original FileSystemFormat

       Write-Verbose "Reloading $pshome\filesystem.format.ps1xml"

       Update-FormatData -prepend $pshome\filesystem.format.ps1xml -ea "SilentlyContinue"

One thing to be aware of is that if pipe owner information to another cmdlet, the original formatting will be used so you are back to needing to specify the Owner information.

captured_Image.png[10]

After you install the script and PS1XML file, be sure to look at the help examples.

After you download the zip file with the script and ps1xml files, be sure to look at the README file. Enjoy and as always I hope you'll let me know what you think.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

1 thought on “All Hail Dir UseALot!”

  1. Jeffery Hicks says:
    November 16, 2009 at 3:02 pm

    I forgot to thank Wes Stahler for kicking my script around. But if you find a bug or problem, it’s mine.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d