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.
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.
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"
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.
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.
I forgot to thank Wes Stahler for kicking my script around. But if you find a bug or problem, it’s mine.