Listing 1: D.ps1
# d.ps1
# Written by Bill Stewart (bill.stewart@frenchmortuary.com)
#
# Lists items like Cmd.exe's Dir command. I wrote this script because the
# get-childitem cmdlet lacks some of Dir's built-in functionality, and I wanted
# to quickly specify attributes and/or a sorting order without the tedium of
# constructing a where-object filter and/or sort-object hashtables.
#
# When passing parameters to the script, I recommend you use the form
# -parameter:argument (particularly with -attributes, -order, and -timefield)
# due to potential argument conflicts. For example, to list files without the
# archive attribute, you should write '-a:-a'. If you just write '-a -a',
# PowerShell's parser interprets this as the -a parameter specified twice. (You
# can also write -a '-a' or -a "-a", but -a:-a is shorter.)
param ($Path,
$Attributes,
$Order,
$TimeField,
[Switch] $FullName,
[Switch] $Recurse,
[Switch] $Bare,
[Switch] $Q,
[Switch] $LiteralPath,
[Switch] $DefaultOutput,
[Switch] $Help)
# Begin Callout A
# Outputs a usage message and exits.
function usage {
$scriptname = $SCRIPT:MYINVOCATION.MyCommand.Name
"NAME"
" $scriptname"
""
"SYNOPSIS"
" Lists items in one or more paths."
""
"SYNTAX"
" $scriptname [-path:] [-attributes:] [-order:]"
" [-timefield:] [-fullname] [-recurse] [-bare] [-q] [-literalpath]"
" [-defaultoutput]"
""
"PARAMETERS"
" -path:"
" The path(s) to the item(s) to list. Without -defaultoutput, the path(s)"
" must be in the file system."
""
" -attributes:"
" Displays items matching any one or more of the following attributes:"
" A Files ready for archiving L Links (reparse points)"
" D Directories N Normal (no other attributes)"
" H Hidden files/directories R Read-only files/directories"
" I Not content-indexed S System files/directories"
" Prefix an attribute character with '-' to exclude it. Use an empty"
" string (') to include all attributes."
""
" -order:"
" Displays items in sorted order."
" D Date (oldest first) N Name (alphabetic)"
" E Extension (alphabetic) S Size (smallest first)"
" G Group directories"
" Prefix a sort order character with '-' to reverse the order. Items are"
" sorted in the order specified."
""
" -timefield:"
" Controls which time field is displayed and/or used for sorting."
" A Last access time W Last write time"
" C Creation time"
""
" -fullname"
" Displays items' full names."
""
" -recurse"
" Recurse through subdirectories. Note: -recurse enables -fullname. When"
" using -recurse, -path must contain only directory names."
""
" -bare"
" Displays items' names only."
""
" -q"
" Displays the owner for each item."
""
" -literalpath"
" Specifies that paths are literal (i.e., no characters are interpreted"
" as wildcards)."
""
" -defaultoutput"
" Outputs objects instead of formatted strings."
exit
}
# End Callout A
# Begin Callout B
# If $expr is True, execute $t; otherwise, execute $f.
function iif([ScriptBlock] $expr, [ScriptBlock] $t, [ScriptBlock] $f) {
if (& $expr) {
& $t
} else {
& $f
}
}
# End Callout B
# Begin Callout C
# Based on the specified attribute string, this function returns two bitmap
# values. The first bitmap contains the attributes to be included, and the
# second bitmap contains the attributes to be excluded.
function get-attributeflags($attrString) {
# Create hash table containing the list of file system attributes.
$attrHash = @{"A" = [System.IO.FileAttributes]::Archive;
"D" = [System.IO.FileAttributes]::Directory;
"H" = [System.IO.FileAttributes]::Hidden;
"I" = [System.IO.FileAttributes]::NotContentIndexed;
"L" = [System.IO.FileAttributes]::ReparsePoint;
"N" = [System.IO.FileAttributes]::Normal;
"R" = [System.IO.FileAttributes]::ReadOnly;
"S" = [System.IO.FileAttributes]::System}
$includeFlags = 0 # Attributes to be included
$excludeFlags = 0 # Attributes to be excluded
# Create a string containing a list of valid attribute characters.
$attrChars = ""
$attrHash.Keys | foreach-object { $attrChars += $_ }
# Keep track of whether '-' appears before an attribute character.
$enableFlag = $TRUE
# Iterate the attribute string as a character array.
foreach ($attrChar in [Char[]] $attrString) {
switch -wildcard ($attrChar) {
"-" {
if ($enableFlag) {
$enableFlag = $FALSE
}
}
"[$attrChars]" {
$flag = $attrHash["$_"]
if ($enableFlag) {
# Set the bit in the "include" bits.
$includeFlags = $includeFlags -bor $flag
# Clear the bit in the "exclude" bits.
$excludeFlags = $excludeFlags -band (-bnot $flag)
} else {
$enableFlag = $TRUE
# Set the bit in the "exclude" bits.
$excludeFlags = $excludeFlags -bor $flag
# Clear the bit in the "include" bits.
$includeFlags = $includeFlags -band (-bnot $flag)
}
}
default {
# Throw an error if the attribute character is not valid.
throw "Invalid attribute character ('$_'). Use -help for help."
}
}
}
# Output both bit flags.
$includeFlags,$excludeFlags
}
# End Callout C
#Begin Callout D
# Outputs a list of sort-order hash tables based on the specified sort-order
# string, name field, and time field.
function get-orderlist($orderString, $nameField, $timeField) {
$orderHash = @{"D" = $timeField;
"E" = "Extension";
"N" = $nameField;
"S" = "Length"}
# Create string containing a list of valid sort-order characters.
$orderChars = ""
$orderHash.Keys | foreach-object { $orderChars += $_ }
# Keep track of whether '-' appears before a sort-order character.
$ascendingSort = $TRUE
# Iterate the sort-order string as a character array.
foreach ($orderChar in [Char[]] $orderString) {
switch -wildcard ($orderChar) {
"-" {
if ($ascendingSort) {
$ascendingSort = $FALSE
}
}
"[$orderChars]" {
# Output a hashtable containing the requested sort order.
@{"Expression" = $orderHash["$_"];
"Ascending" = $ascendingSort}
$ascendingSort = $TRUE
}
"G" {
# Group directories: Sort by the Directory attribute.
@{"Expression" = {($_.Attributes -band
[System.IO.FileAttributes]::Directory) -ne 0};
"Ascending" = -not $ascendingSort}
$ascendingSort = $TRUE
}
default {
throw "Invalid sort-order character ('$_'). Use -help for help."
}
}
}
}
# End Callout D
# Begin Callout E
# Returns the provider name for the specified path. If the path doesn't exist,
# the function returns a blank string.
function get-providername($path) {
$result = ""
$pathArg = iif { $LiteralPath } { "-literalpath" } { "-path" }
$ErrorActionPreference = "SilentlyContinue"
if (invoke-expression "test-path $pathArg `$path") {
$result = (invoke-expression ("get-item $pathArg `$path -force |" +
" select-object -f 1")).PSProvider.Name
}
$result
}
# End Callout E
function main {
# Display the usage message if -help exists.
if ($Help) {
usage
}
# If -path is missing, assume the current location.
if ($Path -eq $NULL) {
$Path = (get-location).Path
}
# Use -literalpath if requested; otherwise, just use -path.
$pathArg = iif { $LiteralPath } { "-literalpath" } { "-path" }
# If -attributes exists, retrieve the bitmap values.
if ($Attributes -ne $NULL) {
$attrInclude,$attrExclude = get-attributeflags $Attributes
}
# If -timefield exists, make sure it's valid. LastWriteTime is the default.
if ($TimeField -ne $NULL) {
switch -wildcard ($TimeField) {
"A*" { $TimeField = "LastAccessTime" }
"C*" { $TimeField = "CreationTime" }
"W*" { $TimeField = "LastWriteTime" }
default {
throw "Invalid time field ('$TimeField'). Use -help for help."
}
}
} else {
$TimeField = "LastWriteTime"
}
# Use the FullName property if requested or if using -recurse.
$nameField = iif { $FullName -or $Recurse } { "FullName" } { "Name" }
# If -order exists, retrieve the sort order.
if ($Order -ne $NULL) {
$Order = get-orderlist $Order $nameField $TimeField
}
# Begin Callout F
# Create the pipeline for the get-childitem cmdlet.
$pipeline = ""
# Add -recurse if requested.
if ($Recurse) {
$pipeline += " -recurse"
}
# If -attributes exists, use -force.
if ($Attributes -ne $NULL) {
$pipeline += " -force"
# If any attributes were specified, pipe to a where-object scriptblock.
if (($attrInclude -ne 0) -or ($attrExclude -ne 0)) {
$pipeline += " | where-object { "
if (($attrInclude -ne 0) -and ($attrExclude -ne 0)) {
$pipeline += "((`$_.Attributes -band $attrInclude) -eq $attrInclude) -and " +
"((`$_.Attributes -band $attrExclude) -eq 0)"
} elseif ($attrInclude -ne 0) {
$pipeline += "(`$_.Attributes -band $attrInclude) -eq $attrInclude"
} else {
$pipeline += "(`$_.Attributes -band $attrExclude) -eq 0"
}
$pipeline += " }"
}
}
# Pipe to sort-object if needed.
if ($Order -ne $NULL) {
$pipeline += " | sort-object `$Order"
}
# End Callout F
# If -defaultoutput exists, execute the expression and return.
if ($DefaultOutput) {
invoke-expression "get-childitem $pathArg `$Path $pipeline"
return
}
# Create the formatted string expression.
$formatStr = "`"{0,5} {1,17} {2,8} {3,15:N0}"
$formatStr += iif { -not $Q } { " {4}" } { " {4,-22} {5}" }
$formatStr += "`" -f `$_.Mode," +
"`$_.$TimeField.ToString('d')," +
"`$_.$TimeField.ToString('t')," +
"`$_.Length"
if ($Q) {
$formatStr += ",(get-acl `$_.FullName).Owner"
}
$formatStr += ",`$_.$nameField"
# Initialize the counters.
$dirCount = $fileCount = $sizeTotal = 0
# Iterate each path. Paths must be in the file system.
foreach ($item in $Path) {
switch (get-providername $item) {
"FileSystem" {
invoke-expression "get-childitem $pathArg `$item $pipeline" |
foreach-object {
if (-not $Bare) {
invoke-expression $formatStr
if (($_.Attributes -band [System.IO.FileAttributes]::Directory) -eq 0) {
$fileCount += 1
$sizeTotal += $_.Length
} else {
$dirCount += 1
}
} else {
$_.$nameField
}
}
}
"" {
write-error "Cannot find path '$item' because it does not exist."
}
default {
write-error "The path '$item' is not in the file system."
}
}
}
# Output footer information when not using -bare.
if (-not $Bare) {
if (($fileCount -gt 0) -or ($dirCount -gt 0)) {
"{0,16:N0} file(s) {1,16:N0} byte(s)`n{2,16:N0} dir(s)" -f
$fileCount,$sizeTotal,$dirCount
}
}
}
main