I want to list all my PowerShell functions from one directory. The following command works:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse | ForEach-Object {Get-Command -Module $_.BaseName}
Now I tried to pipe the output from Get-ChildItem directly to the cmdlet Get-Command. Something like this, which does not work:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse | Get-Command -Module {$_.BaseName}
Obviously, I do not really understand how to pipe the object from Get-ChildItem in the correct way to the parameter -Module in Get-Command.
I have two questions:
Do you have a hint how to pipe correctly?
Is it possible to pipe to a specific parameter like -Module or is the object always handed over to one default parameter?
Parameters can be bound in four different ways:
By location in the argument list, e.g., Get-ChildItem C:\ (only certain parameters)
By name in the argument list, e.g. Get-ChildItem -Path C:\
By value from the pipeline, e.g. 1..5 | Get-Random (only certain parameters)
By name from the pipeline, e.g. 'C:\Windows' | Get-ChildItem (only certain parameters)
You can inspect the various ways of parameter binding via Get-Help <command> -Parameter *. You can then see that Get-Command allows the Module parameter to be bound only by property name:
-Module [<String[]>]
Specifies an array of modules. ...
Required? false
Position? named
Default value none
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? false
So the input has to be an object that has a Module property, to allow binding. In your case you thus need an additional step in between:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse |
Select-Object #{l='Module';e={$_.Basename}} |
Get-Command
Now, this instance here is something that's a bit annoying, since the Module parameter is bound by property name, but most things don't give you an object with a Module property. Heck, even Get-Module doesn't have that, since the returned object uses Name as the property name, so you can't even do
Get-Module | Get-Command
However, in many other places (notably concerning paths) work very well automatically. And if you can control your input objects, e.g. when reading from CSV or other data sources, you can end up with rather nice and concise code.
EDIT: Ansgar Wiechers notes that, while this should work, it doesn't, actually. This may be a shortfall of PowerShell's parameter binding algorithm (which is quite complex, as seen above, and we never got it to work correctly in Pash either), or maybe the Get-Command cmdlet has parameters described in a way that simply cannot allow binding because of reasons.
Related
Intro
On Linux, I'll often use something like this to see the recently changed files in a directory with many files:
ls -t | head
I can do the following in PowerShell which is similar:
Get-ChildItem | Sort-Object -Property LastWriteTime | Select-Object -Last 15
That's a bit long so I then have the following in $PROFILE:
function Recent ()
{
Get-ChildItem | Sort-Object -Property LastWriteTime | Select-Object -Last 15
}
And maybe also:
Set-Alias lst Recent
or
Set-Alias ls-t Recent
as a shorter variant.
Question
Is there a built-in way to list the recently changed files that's more concise than the approach I've shown above?
Is there some other best practice that y'all would recommend?
As already presented in the comments,
You can go from :
Get-ChildItem | Sort-Object -Property LastWriteTime | Select-Object -Last 15
to
gci | Sort-Object LastWriteTime | Select -l 15
What is at play ?
gci is an alias for Get-ChildItem. To view all aliases available, you can type Get-Alias in your current session.
Sort-Object LastWriteTime make use of positional arguments. When an unnamed argument is given to a Powershell cmdlet, it is mapped to the first positional parameter.
Select -l 15 -l stand for -last. This work because when getting a parameter that does not exist, Powershell will attempt to map it to the closest matching parameter. In all the parameter available with the Select-Object cmdlet, only -last can be matched (no other parameter for that cmdlet start with the letter L. Note that in this case, l
is not defined as an alias for last. It is Powershell parameter disambiguation.
Best practices
What you do in your session stay in your session.
You can use aliases, parameter disambiguation as much as you please.
That being said, when developing a script or a module, you should avoid using aliases, disambiguated parameters and positional parameter altogether.
Some kind of problems that might occurs.
Parameter disambiguation might fail if the cmdlet introduce another parameter that could also be a match. For instance Get-Service -inputObject something work well. Get-Service -in "test" will fail as it is ambiguous. -in can match -inputObject but also -include. And while Get-Service -inp "test" would work, it is not very readable compared to simply using the full parameter name.
Aliases might not be available cross-platform. For instance, while sort work as an alias for sort-object in Windows, it does not in Linux (as it is a native command there). This kind of differentiation might produce unexpected results and break your script depending on context. Also, some aliases might be dropped in the future and they do make the script less readable)
Finally, positional parameters should also be avoided in scripts & modules.
Using named parameter will make your scripts more clear and readable for everyone.
To summarize, while working in a session, you can use aliases, parameter disambiguation and positional parameter as you please but when working on scripts or modules, they should be avoided.
References
Select-Object
Select-Object
[-InputObject ]
[[-Property] <Object[]>]
[-ExcludeProperty <String[]>]
[-ExpandProperty ]
[-Unique]
[-Last ]
[-First ]
[-Skip ]
[-Wait]
[]
Types of Cmdlet Parameters
A positional parameter requires only that you type the arguments in
relative order. The system then maps the first unnamed argument to the
first positional parameter. The system maps the second unnamed
argument to the second unnamed parameter, and so on. By default, all
cmdlet parameters are named parameters.
Powershell Parameter Disambiguation and a surprise
For instance, instead of saying Get-ChildItem -Recurse, you can say
Get-ChildItem -R. Get-ChildItem only has one (non-dynamic) parameter
that started with the letter ‘R’.. Since only one parameter matches,
PowerShell figures you must mean that one. As a side note, dynamic
parameters like -ReadOnly are created at run-time and are treated a
bit differently.
I would do:
ls | sort lastw*
or
ls | sort lastw <#press tab#>
The most recent ones appear at the bottom anyway.
This one stumps me a bit. I generally feel pretty advanced in powershell but I simply dont understand the nuance of this one.
This works
$LogFiles = Get-ChildItem -Path c:\windows\temp\*.log,c:\temp\*.log,C:\programdata\Microsoft\IntuneManagementExtension\Logs\*.log
Yet what I want to do (and doesnt work) is this:
$LogsToGather = "c:\windows\temp\*.log,c:\temp\*.log,C:\programdata\Microsoft\IntuneManagementExtension\Logs\*.log"
$LogFiles = Get-ChildItem -Path "$($LogsToGather)" -Recurse
I have tried making the VAR an array, I have tried a number of things with making string. I was able to write around the issue but I am uniquely interested in understanding what data type -path is accepting with that common delineation and be able to create it dynamically.
It seems like a trick that the cmdlet accepts comma delineation. Can it be recreated using some sort of array, hashtable, etc..?
Anyone know?
Yes, $LogsToGather must be an array of strings for your command to work:
$LogsToGather = 'c:\windows\temp\*.log', 'c:\temp\*.log', 'C:\programdata\Microsoft\IntuneManagementExtension\Logs\*.log'
Note how the array elements, separated by ,, must be quoted individually (see bottom section).
Get-Help with -Parameter is a quick way to examine the data type a given parameter expects:
PS> Get-Help Get-ChildItem -Parameter Path
-Path <String[]>
Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (`.`).
Required? false
Position? 0
Default value Current directory
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? false
String[] represents an array ([]) of [string] (System.String) instances - see about_Command_Syntax.
For more information on Get-ChildItem, see the docs.
As for what you tried:
$LogsToGather = "c:\windows\temp\*.log,c:\temp\*.log,C:\programdata\Microsoft\IntuneManagementExtension\Logs\*.log"
This creates a single string that, when passed to Get-ChildItem, is as a whole interpreted as a single path, which obviously won't work.
Note that specifying the elements of an array unquoted, as in:
Get-ChildItem -path c:\windows\temp\*.log, c:\temp\*.log, ...
is only supported if you pass an array as a command argument, not in the context of creating an array with an expression such as $LogsToGather = 'foo', 'bar', ..
The reason is that PowerShell has two fundamental parsing modes - argument mode and expression mode, as explained in this answer,
Have written a function in powershell with parameters. Would like to use cmdlet Show-command to show UI of the function. But my troubles are to show the function with already pre-filled parameters of my function.
This works well:
Show-Command Get-ChildItem
What I need is:
Show-Command Get-ChildItem -Path "c:\windows"
So the UI of the cmdlet Get-ChildItem should have pre-filled the path parameter of Get-ChildItem cmdlet.
I understand that "-Path" is not a parameter of Show-Command and therefore it doesn't work. But is there any idea or workaround?
Have been trying also to use the the $PSDefaultParameterValues like this:
$PSDefaultParameterValues = #{"Get-childitem:Path" = 'c:\windows'}
Get-ChildItem
this lists the Windows directory correctly, but next command:
Show-Command Get-ChildItem
does not pre-fill the parameter of Path to c:\windows
How to obtain all the items except the one indicated by a switch?
I use:
Get-Disk -UniqueId 4875E7EB064AA60
to get information only a specific disk drive.
I want to use the same command, but get all drives except this one.
Something like this (in pseudo code):
Get-Disk -not( -UniqueId 4875E7EB064AA60 )
PowerShell as a language does not allow for "inverting" a parameter value. You need to filter the returned results with Where-Object after the fact.
Get-Disk | Where-Object { $_.UniqueId -ne '4875E7EB064AA60' }
There are several cmdlets that do allow expressing "everything but this" semantics, but it's up to the individual cmdlet if and how they implement that. For example:
The Get-ChildItem and Select-Object cmdlets have a parameter -Exclude that allows to exclude particular results.
The Select-String cmdlet has a switch -NotMatch to invert what is selected by the parameter -Pattern.
All cmdlets with filter parameters that allow expressing a not condition (like Get-WmiObject -Filter, Get-ADUser -Filter, or Get-ADUser -LDAPFilter) obviously also allow expressing a "not this" semantic.
EDIT Hoping here to clarify my convoluted and misleading question... based on my mistaken assumption that -file accepts inputs. Thanks for setting me straight and pointing out that it's just a switch parameter; the inputs in my example actually get passed to -path. Sounds like that may be the fastest purely powershell way to search for multiple file types, since -filter accepts only a single input and -include is slower.
The get-childItem documentation says "Filters are more efficient than other parameters, because the provider applies them when retrieving the objects, rather than having Windows PowerShell filter the objects after they are retrieved."
v3 has a new parameter set with a -file parameter, probably meant for excluding directories, to match cmd.exe's dir /a:-d
Like -include and unlike -filter, -file accepts multiple, as in gci -file "*.ldf","*.bak"
So i'm wondering, and have thus far failed to reliably test, if -file is like -filter from a performance perspective, ie "more efficient", or more like the "other parameters" like -include. If -file is a filter, that's nice because afaict -filter only handles one filter at a time, so if you want multiple filters (like *.ldf and *.bak) then you need to either run gci -filter twice or use -include instead. So I'm wondering if -file lets us reap the efficiency benefits of a filter, for multiple filters.
I stumbled on some error text that's got me optimistic. The -file parameter wants -path to be the current directory, so this gci -path $path -file "*.bak","*.ldf" gives an error. Push-location seems like a viable workaround, but here I'm more interested in the content of the error text:
Get-ChildItem : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Filter'. Specified method is not supported.
I called -file but the error complains about "parameter 'Filter'". So maybe -file is efficient like a filter? OTOH, -filter doesn't need -path to be the current directory, so in that respect -file is more like -include.
just one precision:
gci -path $path -file "*.bak","*.ldf"
-file is a switch parameter (as -directory is) and doesn't accept values (To get only files, use the File parameter and omit the Directory parameter. To exclude files, use the Directory parameter and omit the File parameter); then the "*.bak","*.ldf" are implicitly passed to -filter as value, and filter accept only string and not string[]. That's where your error came from.
Regarding performance: using -file or -directory is faster than using a where-object psiscontainer or ? { !$_.psiscontainer} because it's done at provider (filesystem in this case) level.