A function "alias" that needs to pass along parameters - powershell

I have put the following into my $PROFILE:
function dir
{
Get-ChildItem -Force $args
}
What I want is a simple "dir" command that lists all files, including hidden and system files.
However, as soon as I pass some parameters along:
dir \ -Directory
I am rewarded with a full listing of my root followed by this error:
Get-ChildItem : Cannot find path 'C:\Users\myuser-Directory' because
it does not exist.
My intention was of course that my little homebrewed "alias" would expand into this:
Get-ChildItem -Force \ -Directory
But instead "-Directory" gets treated as a string literal. How can I make my dir function pass arguments the way I intended (rather than as an array of string)?

$args is an array. If you use it like this:
Get-ChildItem -Force $args
it's the same as if you did this:
Get-ChildItem -Force -Path '\', '-Directory'
which works (in a way), because the parameter -Path accepts array input.
Use splatting to avoid this pitfall:
Get-ChildItem -Force #args

Related

Need PowerShell alias for cargo commands [duplicate]

I want to create an alias in Windows PowerShell to delete multiple folders from the command line.
To remove more than one item I call:
Remove-Item '.\Documents\*\Bin\' ,'.\Documents\*\Inter\' -Force -Recurse
I have tried to create the alias like this:
New-Alias -Name 'Clean-RCD' Remove-Item '.\Documents\*\Bin\' ,'.\Documents\*\Inter\' -Force -Recurse
Output:
New-Alias: A positional parameter cannot be found that accepts argument 'System.Object[]'.
Any idea how to define this alias correctly?
Unlike in bash, aliases in PowerShell are strict 1-to-1 command name mappings - no extra parameter arguments allowed.
You'll want to create a function instead:
function Clean-RCD {
Remove-Item -Path '~\Documents\*\Bin', '~\Documents\*\Inter\' -Force -Recurse
}
Use of ~ (which resolves to your home folder) over . is intentional - this way it'll still work if you've navigated to a different path

Get-Item : Cannot find path because it does not exist when piped from Get-ChildItem [duplicate]

Function Zip
{
Param
(
[string]$zipFile
,
[string[]]$toBeZipped
)
$CurDir = Get-Location
Set-Location "C:\Program Files\7-Zip"
.\7z.exe A -tzip $zipFile $toBeZipped | Out-Null
Set-Location $CurDir
}
$Now = Get-Date
$Days = "60"
$TargetFolder = "C:\users\Admin\Downloads\*.*"
$LastWrite = $Now.AddDays(-$Days)
$Files = Get-Childitem $TargetFolder -Recurse | Where {$_.LastWriteTime -le "$LastWrite"}
$Files
Zip C:\Users\Admin\Desktop\TEST.zip $Files
I am testing out this script I found online. My problem is that instead of zipping the files in the target folder, it is copying and zipping the contents of the 7-zip program file folder. What could cause this? Thanks in advance
Pass the files as full paths to the Zip function, using their .FullName property (PSv3+ syntax):
Zip C:\Users\Admin\Desktop\TEST.zip $Files.FullName
The problem is that, in Windows PowerShell, the [System.IO.FileInfo] instances returned by Get-ChildItem situationally[1] stringify to their file names only, which is what happened in your case, so your Zip function then interpreted the $toBeZipped values as relative to the current location, which is C:\Program Files\7-Zip at that point.
That said, it's better not to use Set-Location in your function altogether, so that in the event that you do want to pass actual relative paths, they are correctly interpreted as relative to the current location:
Function Zip {
Param
(
[Parameter(Mandatory)] # make sure a value is passed
[string]$zipFile
,
[Parameter(Mandatory)] # make sure a value is passed
[string[]]$toBeZipped
)
# Don't change the location, use & to invoke 7z by its full path.
$null = & "C:\Program Files\7-Zip\7z.exe" A -tzip $zipFile $toBeZipped
# You may want to add error handling here.
}
[1] When Get-ChildItem output stringifies to file names only:
Note:
If Get-ChildItem output is to be passed to other file-processing cmdlets, say Rename-Item, the problem can be bypassed by providing input to them via the pipeline, which implicitly binds to the target cmdlet's -LiteralPath parameter by full path - see this answer for more information.
The related Get-Item cmdlet output always stringifies to the full path, fortunately.
In PowerShell (Core) v6.1+, Get-ChildItem too always stringifies to the full path, fortunately.
The following therefore only applies to Get-ChildItem in Windows PowerShell:
The problem is twofold:
Even PowerShell's built-in cmdlets bind file / directory arguments (parameter values - as opposed to input via the pipeline) not as objects, but as strings (changing this behavior is being discussed in GitHub issue #6057).
Therefore, for robust argument-passing, you need to ensure that your Get-ChildItem output consistently stringifies to full paths, which Get-ChildItem does not guarantee - and it's easy to forget when name-only stringification occurs or even that you need to pay attention to it at all.
Always passing the .FullName property values instead is the simplest workaround or, for reliable operation with any PowerShell provider, not just the filesystem, .PSPath.
[System.IO.FileInfo] and [System.IO.DirectoryInfo] instances output by a Get-ChildItem command stringify to their file names only, if and only if:
If one or more literal directory paths are passed to -LiteralPath or -Path (possibly as the 1st positional argument) or no path at all is passed (target the current location); that is, if the contents of directories are enumerated.
and does not also use the -Include / -Exclude parameters (whether -Filter is used makes no difference).
By contrast, whether or not the following are also present makes no difference:
-Filter (optionally as the 2nd positional argument, but note that specifying a wildcard expression such as *.txt as the 1st (and possibly only) positional argument binds to the -Path parameter)
-Recurse (by itself, but note that it is often combined with -Include / -Exclude)
Example commands:
# NAME-ONLY stringification:
Get-ChildItem | % ToString # no target path
Get-ChildItem . | % ToString # path is literal dir.
Get-ChildItem . *.txt | % ToString # path is literal dir., combined with -Filter
# FULL PATH stringification:
Get-ChildItem foo* | % ToString # non-literal path (wildcard)
Get-ChildItem -Recurse -Include *.txt | % ToString # use of -Include
Get-ChildItem file.txt | % ToString # *file* path
If you (temporarily) disable the |Out-Null you'll see what error msg pass along.
$Files contains objects not just an array of file names.
By default powershell tries to stringify this using the Name property which doesn't contain the path - so 7zip can't find the files as you also change the path to the 7zip folder (and -recurse collecting $files)
So change the line
$Files = Get-Childitem $TargetFolder -Recurse | Where {$_.LastWriteTime -le "$LastWrite"}
and append
| Select-Object -ExpandProperty FullName
A slightly reformatted verson ofyour source:
Function Zip{
Param (
[string]$zipFile,
[string[]]$toBeZipped
)
& "C:\Program Files\7-Zip\7z.exe" A -tzip $zipFile $toBeZipped | Out-Null
}
$Days = "60"
$LastWrite = (Get-Date).Date.AddDays(-$Days)
$TargetFolder = "$($ENV:USERPROFILE)\Downloads\*"
$Files = Get-Childitem $TargetFolder -Recurse |
Where {$_.LastWriteTime -le $LastWrite} |
Select-Object -ExpandProperty FullName
$Files
Zip "$($ENV:USERPROFILE)\Desktop\TEST.zip" $Files

Ambiguous behavior by Get-ChildItem cmdlet in Powershell

I'm trying to get a list of all txt files in a local folder.
$dir = "C:\report\"
Get-ChildItem -Path $dir -File -Include "*.txt"
Above code returns nothing, However when I add wildcard * in front of the path, the command works as expected and returns the list of txt files.
$dir = "C:\report\*"
Get-ChildItem -Path $dir -File -Include "*.txt"
Without the -Include "*.txt" parameter, I get list of all files in both the cases (System.IO.FileInfo object)
I'm wondering why adding -Include "*.txt" is causing this ambiguous behavior?
I'm new to PowerShell and I'm using Powershell 7.
Thanks in advance.
The Include parameter works when combined with theRecurse switch, OR as you have noticed by appending \* to the path, which implies recursion.
If you do not want the cmdlet to recurse through subfolders, use -Filter '*.txt' instead.

How to supply argument to value of New-Alias command?

I wish Get-ChildItem -force to get executed when I type ll and I have this in my profile.ps1:
New-Alias -Name ll -Value Get-ChildItem -force
However, when I type ll, I can see that the -force argument is not being used. What am I doing wrong?
Edit: What I really wish to achieve is to show all files in a folder, even if they're hidden. And I wish to bind this to ll.
You cannot do that with aliases. Aliases are really just different names for commands, they cannot include arguments.
What you can do, however, is, write a function instead of using an alias:
function ll {
Get-ChildItem -Force #args
}
You won't get tab completion for arguments in that case, though, as the function doesn't advertise any parameters (even though all parameters of Get-ChildItem get passed through and work). You can solve that by effectively replicating all parameters of Get-ChildItem for the function, akin to how PowerShell's own help function is written (you can examine its source code via Get-Content Function:help).
To add to Joey's excellent answer, this is how you can generate a proxy command for Get-ChildItem (excluding provider-specific parameters):
# Gather CommandInfo object
$CommandInfo = Get-Command Get-ChildItem
# Generate metadata
$CommandMetadata = New-Object System.Management.Automation.CommandMetadata $CommandInfo
# Generate cmdlet binding attribute and param block
$CmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($CommandMetadata)
$ParamBlock = [System.Management.Automation.ProxyCommand]::GetParamBloc($CommandMetadata)
# Register your function
$function:ll = [scriptblock]::Create(#'
{0}
param(
{1}
)
$PSBoundParameters['Force'] = $true
Get-ChildItem #PSBoundParameters
'#-f($CmdletBinding,$ParamBlock))

'Get-ChildItem -Include/-Exclude' in PowerShell doesn't filter if passed as an argument to a function

I have a problem getting a filter argument to Get-ChildItem in a function.
The following works fine and displays a whole list of files:
c:\temp\Get-ChildItem -Include *deleteme*.txt -Recurse
Now say I have the following script
#file starts here
#filename = GetLastFile.ps1
param([string] $filter)
$files = Get-ChildItem $filter
Write-Host $files #should print all matching files but prints nothing
$file = $files | Select-Object -Last 1;
$file.name #returns filename
#File ends here
Now trying to run the script,
c:\temp.\GetLastFile.ps1 "-Include *deleteme*.txt -Recurse"
returns nothing.
Supplying a filter, *.*, works fine. It seems to be failing due to the -Include or -Exclude. Any ideas?
You're starting to get into an area where where Powershell 2.0 proxy functions can help. However, short of that, here's a simple way in PowerShell 2.0 to do this assuming all you need is -Include and -Recurse. Actually, I would recommend using -Filter instead it will do what you want and frankly it's quite a bit faster (4x on some of my tests) because -filter uses filesystem filtering provided by the OS whereas -include is processed by PowerShell.
param([string]$Filter, [switch]$Recurse)
$files = Get-ChildItem #PSBoundParameters
Write-Host $files #should print all matching files but prints nothing
$file = $files | Select-Object -Last 1;
$file.name #returns filename
The # symbol is used to "splat" an array or hashtable across the parameters to a command. The $PSBoundParameters variable is an automatic variable new to PowerShell 2.0 that is defined in functions. It's a hashtable that contains all the bounded (named and positional) parameters e.g.:
PS> function foo($Name,$LName,[switch]$Recurse) { $PSBoundParameters }
PS> foo -Name Keith Hill -Recurse
Key Value
--- -----
Name Keith
Recurse True
LName Hill
When you splat a hashtable like this against a command, PowerShell will map the key's (e.g. Recurse) value to the parameter named Recurse on the command.
I believe what is happening is your $filter parameter is being treated as a single string argument to the Get-ChildItem command. As such, unless you have a directory named "-Include deleteme.txt -Recurse", the command will always return nothing.
As for fixing your problem, well, there are a bunch of ways you could approach it. Probably one of the more versatile ways is to switch program behavior if the the $filter argument is passed, and instead of passing the entire filter string just pass the "deleteme.txt" string.
You can use Invoke-Expression to execute a command stored in a variable. For example:
param([string] $filter)
$files = Invoke-Expression "Get-ChildItem $filter"
Write-Host $files
$file = $files | Select-Object -Last 1
$file.name