I've often told people that I spend my day in a PowerShell prompt. I run almost my entire day with PowerShell. I've shared many of the tools I use daily on Github. Today, I want to share another way I have PowerShell work the way I need it, with minimal effort. This specific task centers on files and folders.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
As you might expect, I am constantly creating, editing, and managing files. I do all of this from a PowerShell prompt. I rarely use the start menu to find a program to launch. My challenge has always been finding the files and folders I've recently been using. Get-ChildItem is naturally the PowerShell tool of choice, but I've finally gotten around to making it work the way I need.
Get-ChildItem Defaults
The default behavior for Get-ChildItem has always been to sort by name.
Directories are listed first, sorted by name, and then files, also sorted by name. Often, I'm trying to remember what folder I was using, and this output doesn't make that easy for me. What I need is a sort by the LastWriteTime property. But I still want folders listed first, followed by files.
Creating a New Command
I can't use a custom format view because that won't do the sorting. In addition, I want to make this an easy process. I know I can run a PowerShell expression like this to get the desired result.
Get-ChildItem | Sort-Object -Property { -Not $_.psiscontainer }, LastWritetime
Sort-Object is sorting pipeline input on two properties. The first is a custom property defined on-the-fly by the scriptblock. The scriptblock looks at the PSIsContainer property, a Boolean value. Files will have a value of True and folders a value of False. I want folders to display first, so I'm using the -Not operator to invert the value. After items are sorted on this property, they are sorted on the LastWriteTime. This makes my life much, much easier. But I'm lazy. I don't want to remember to type all of this, even with auto-completion via PSReadline.
How about a function?
Function Get-MyFolderItem {
[cmdletbinding()]
[alias("dl")]
Param(
[Parameter(Position = 0)]
[string]$Path,
[Parameter(Position = 1)]
[string]$Flter,
[switch]$File,
[switch]$Directory,
[string[]]$Exclude,
[string[]]$Include,
[switch]$Recurse
)
#Run Get-Childitem with whatever parameters are specified.
Get-ChildItem @psboundparameters | Sort-Object -Property { -Not $_.psiscontainer }, LastWritetime
}
This function is nothing more than a wrapper around Get-ChildItem. My function parameters mirror those of Get-ChildItem. I'm splatting $PSBoundParameters to Get-ChildItem and then performing my sort.
$PSBoundParameters is an intrinsic variable. It is a specialized hashtable that contains every parameter with a value. I also added an alias to my function to make this even easier to use. I can put this function in my PowerShell profile script, and I am set.
Listing by Proxy
But there is another way I can create this command. I could make a proxy function. This is a particular type of function. Proxy functions often replace original commands. Typically, you use proxy functions to add or remove parameters or customize a command's behavior. You often see proxy functions with Just Enough Administration (JEA) deployments. But I could use a proxy function built on Get-ChildItem.
The easiest way to get started is with Copy-Command from my PSScriptTools module.
Copy-Command Get-Childitem -AsProxy -UseForwardHelp -IncludeDynamic
Run this command in the VS Code integrated editor, and it will load a new file into the editor.
If you are building proxy functions, pay attention to your PowerShell version. I created my file in PowerShell 7. Fortunately, the parameters are unchanged in Windows PowerShell, so that I can use my proxy function in both versions. But that may always not be the case.
Here's the proxy function with one addition.
#requires -version 5.1
<#
This is a copy of:
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Get-ChildItem 7.0.0.0 Microsoft.PowerShell.Management
Created: 06 June 2022
Author : Jeff Hicks
Created with Copy-Command from the PSScriptTools module
https://github.com/jdhitsolutions/PSScriptTools
Copy-Command Get-Childitem -AsProxy -UseForwardHelp -IncludeDynamic
#>
Function Get-ChildItem {
<#
.ForwardHelpTargetName Microsoft.PowerShell.Management\Get-ChildItem
.ForwardHelpCategory Cmdlet
#>
[CmdletBinding(DefaultParameterSetName = 'Items', SupportsTransactions = $true, HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=113308')]
Param(
[Parameter(ParameterSetName = 'Items', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string[]]$Path,
[Parameter(ParameterSetName = 'LiteralItems', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('PSPath')]
[string[]]$LiteralPath,
[Parameter(Position = 1)]
[string]$Filter,
[switch]$File,
[switch]$Directory,
[string[]]$Include,
[string[]]$Exclude,
[Alias('s')]
[switch]$Recurse,
[uint32]$Depth,
[switch]$Force,
[switch]$Name
)
Begin {
Write-Verbose "[BEGIN ] Starting $($MyInvocation.Mycommand)"
Write-Verbose "[BEGIN ] Using parameter set $($PSCmdlet.ParameterSetName)"
Write-Verbose ($PSBoundParameters | Out-String)
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
#sort the output with directories first and then sorted by last write time
$scriptCmd = { & $wrappedCmd @PSBoundParameters | Sort-Object -Property { -Not $_.psiscontainer }, LastWritetime }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
catch {
throw
}
} #begin
Process {
try {
$steppablePipeline.Process($_)
}
catch {
throw
}
} #process
End {
try {
$steppablePipeline.End()
}
catch {
throw
}
Write-Verbose "[END ] Ending $($MyInvocation.Mycommand)"
} #end
} #end function Get-ChildItem
The proxy function is also "wrapping" Get-ChildItem.
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
All I'm doing is sorting the output.
$scriptCmd = { & $wrappedCmd @PSBoundParameters | Sort-Object -Property { -Not $_.psiscontainer }, LastWritetime }
All I have to do is dot-source the proxy function script. From now on, whenever I run Get-Childitem, it will use my proxied version.
In my proxy function, I haven't changed anything about how the underlying command, Get-ChildItem, runs. All I've done is add sorting. I can continue to use Get-Childitem as I always have. But now, in the file system, I get the desired output.
Summary
Get-Command confirms I am running a custom version of Get-Childitem.
To be honest, I'm not sure which approach I'll use long-term. For now, I'm loading my wrapper function with the dl alias and the proxy function in my PowerShell profile script. I'll have to see if there are any limitations to the proxy function that I haven't considered yet.
If you need to bend a PowerShell command to meet your needs, I hope my examples can serve as templates for solutions.
Update
After using my proxy function for a bit, I realized I neglected to include the dynamic parameters -File and -Directory. I updated the code samples.
3 thoughts on “Using PowerShell Your Way”
Comments are closed.