dir –a:d

dir –a:d

  • Comments 10

In cmd, listing files based on attributes was simple:

only directories dir /a:d
only files (no directories) dir /a:-d
only hidden files dir /a:h

In PowerShell, it’s not so easy:

only directories dir | ? { $_.PSIsContainer }
only files (no directories) dir | ? { !$_.PSIsContainer }
only hidden files dir -force | ? { $_.Attributes –band [IO.FileAttributes]::Hidden }

We have had requests to cover the first case better, for example:

https://connect.microsoft.com/feedback/ViewFeedback.aspx?SiteID=99&FeedbackID=252549&wa=wsignin1.0

https://connect.microsoft.com/feedback/ViewFeedback.aspx?SiteID=99&FeedbackID=308796&wa=wsignin1.0

We haven’t added such a parameter to Get-ChildItem yet, but with some new features in V2, you can add them yourself.

The techniques I describe below can be used to augment any cmdlet, or any script or function for that matter.

If you don’t care much about the actual implementation details, just skip to the bottom and download the module.  To start using it, you can use:

import-module path\to\Get-ChildItem.psm1

(Note that there isn’t any particularly good reason I implemented this as a module, other than as a way of testing multiple features.  It could work equally well as a script or function.)

To get started, we need a proxy to Get-ChildItem.  It should support all of the parameters that Get-ChildItem supports, plus it should support pipelining in the same way as the cmdlet.  We can use the following code to generate a proxy:

$md = new-object System.Management.Automation.CommandMetadata (get-command get-childitem)
[System.Management.Automation.ProxyCommand]::Create($md)

This will return a rather long string, we can capture it in a file and make our changes.

The first thing we need is an additional parameter.  We’ll call it Attribute.  We just add:

[System.String]
$Attribute = '',

to the param block.

Next, we need to add the logic for our new parameter.  To do this, we will replace the Begin block that was generated with the following code:

 

[void]$PSBoundParameters.Remove('Attribute')
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ChildItem',
        [System.Management.Automation.CommandTypes]
::Cmdlet)
if ($Attribute -ne '')
{
    $includeMask = 0
    $excludeMask = 0
    [bool]$onOrOff = $true
    foreach ($char in $Attribute.GetEnumerator())
    {
        if ($char -eq '-') { $onOrOff = $false }
        else {
            if ($flags[$char] -eq $null) {
                throw "Attribute '$char' not supported"
            }
            if ($onOrOff) {
                $includeMask = $includeMask -bor $flags[$char]
            }
            else {
                $excludeMask = $excludeMask -bor $flags[$char]
            }
            $onOrOff = $true
        }
    }

    if ($includeMask -band [IO.FileAttributes]::Hidden) {
        $PSBoundParameters.Force = $true
    }

    $scriptCmd = {& $wrappedCmd @PSBoundParameters |
        ? { $_.PSProvider.Name -ne 'FileSystem' -or
            ((($_.Attributes -band $includeMask) -eq $includeMask) -and
             (($_.Attributes -band $excludeMask) -eq 0))
        }
    }
}
else {
    $scriptCmd = {& $wrappedCmd @PSBoundParameters }
}

$steppablePipeline = $scriptCmd.GetSteppablePipeline()
$steppablePipeline.Begin($PSCmdlet)

 

Note the first thing we do is remove our parameter from PSBoundParameters.  If we don’t do that, the real cmdlet Get-ChildItem will complain about the unknown parameter when it is specified.

I wanted functionality and syntax as close to cmd.exe as possible.  So we should support things like ‘dir –a:h-sr’, which would include hidden read-only files that are not system files.  To do this, I generate bitmasks representing the attributes requested, then filter the output of the actual Get-ChildItem command based on these bitmasks.

Note the script block assigned to $scriptCmd is similar to the one generated by ProxyCommand.Create, but it includes some filtering.  Steppable pipelines can’t be created for arbitrary script blocks, only when there is a single pipeline in the script block.  Fortunately we can do all our filtering in one pipeline.

If you wanted to add sorting to the output, you could extend this pipeline to do the appropriate sorting.  I leave it as an exercise for the reader to add the cmd.exe sorting flags like /OD, /O-D, /OS, /O-S, etc.

ProxyCommand.Create works pretty good, but it doesn’t generate code to support dynamic parameters.  Most cmdlets don’t have dynamic parameters, but Get-ChildItem definitely does, so we want our proxy to support them as well.  Different providers can add dynamic parameters to Get-ChildItem, so the optimal solution is to, at runtime, ask the Get-ChildItem cmdlet what dynamic parameters it has.  We can do that this way:

[void]$PSBoundParameters.Remove('Attribute')
$argList = @($psboundparameters.getenumerator() | % { "-$($_.Key)"; $_.Value })

$wrappedCmd = Get-Command Get-ChildItem -Type Cmdlet -ArgumentList $argList
$providerParams = @($wrappedCmd.Parameters.GetEnumerator() |
                                                  Where-Object { $_.Value.IsDynamic })
if ($providerParams.Length -gt 0)
{
    $paramDictionary = new-object Management.Automation.RuntimeDefinedParameterDictionary
    foreach ($param in $providerParams)
    {
        $param = $param.Value
        $dynParam1 = new-object Management.Automation.RuntimeDefinedParameter `
                                  $param.Name, $param.ParameterType, $param.Attributes
        $paramDictionary.Add($param.Name, $dynParam1)
    }
    return $paramDictionary
}

This code generates an argument list from the automatic variable $PSBoundParameters.  $argList won’t necessarily look exactly like the arguments passed in as it will add the parameter names to positional arguments.

Jason Shirk [MSFT]

 

Attachment: Get-ChildItem.psm1
Leave a Comment
  • Please add 7 and 2 and type the answer here:
  • Post
  • Please just add this parameter to Get-ChildItem. Perhaps it will take even less time then reserving keywords for version after next and writing awkward (to me) workarounds for Get-ChildItem oversights.

    You demonstrate some technique though, thank you for your effort.

  • If it was so easy in cmd, why is it not so easy in powershell?

    I suppose I could make aliases. :)

    Oh. Completely unrelated: Do you have plans to fix the get-help output formatting? Its rather disconcerting to see words broken in the middle and wrapped to the next line.

    PowerShell is most awesome!

  • Are you guys joking here? A blog on how to do directory listing in Powershell while I can do this in one line in cmd?

    I just want to get my job done quickly so please help me out here to abstract me from the unnecessary scripting as possible as possible.

  • Odd you'd pick on those examples since I too found myself missing them almost immediately.  I resolved that want by declaring two new functions in my profile -

    function dir/ad ([switch]$slashAD) {dir -force -recurse:$slashAD | ?{$_.PSIsContainer}}

    function dir/a-d ([switch]$slashAD) {dir -force -recurse:$slashAD | ?{!$_.PSIsContainer}}

  • well here’s one way to make PowerShell act more like CMD when you are looking at listing directories

  • Thanks for this post. I would like more posts on meta-programming

  • If you're a Windows IT Pro subscriber, I wrote a 'dir' emulation script that also works for v1.

    http://windowsitpro.com/article/articleid/101900/

  • I think this was a major oversight, I use the "only directories" filter all the time in cmd and the powershell way is over bloated. That being said I'm still in love with powershell

  • What I did was to simply create a batch file called d.bat. In the batch file I have:

    dir %*

    Now I have full access in PS to the dir command.

  • Brad Thompson's solution saved me a LOT of time.

Page 1 of 1 (10 items)