Negate a -switch (all but the switch parameter) - powershell

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.

Related

Equivalent of `ls -t | head`

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.

Where-Object, Select-Object and ForEach-object - Differences and Usage

Where-Object, Select-Object and ForEach-Object
I am a PowerShell beginner. I don't understand too much. Can someone give examples to illustrate the differences and usage scenarios between them?
If you are at all familiar with either LINQ or SQL then it should be much easier to understand because it uses the same concepts for the same words with a slight tweak.
Where-Object
is used for filtering out objects from the pipeline and is similar to how SQL filters rows. Here, objects are compared against a condition, or optionally a ScriptBlock, to determine whether it should be passed on to the next cmdlet in the pipeline. To demonstrate:
# Approved Verbs
Get-Verb | Measure-Object # count of 98
Get-Verb | Where-Object Verb -Like G* | Measure-Object # 3
# Integers 1 to 100
1..100 | Measure-Object # count of 100
1..100 | Where-Object {$_ -LT 50} | Measure-Object # count of 49
This syntax is usually the most readable when not using a ScriptBlock, but is necessary if you want to refer to the object itself (not a property) or for more complicated boolean results. Note: many resources will recommend (as #Iftimie Tudor mentions) trying to filter sooner (more left) in the pipeline for performance benefits.
Select-Object
is used for filtering properties of an object and is similar to how SQL filters columns. Importantly, it transforms the pipeline object into a new PSCustomObject that only has the requested properties with the object's values copied. To demonstrate:
Get-Process
Get-Process | Select-Object Name,CPU
Note, though, that this is only the standard usage. Explore its parameter sets using Get-Help Select-Object where it has similar row-like filtering capabilities like only getting the first n objects from the pipeline (aka, Get-Process | Select-Object -First 3) that continue onto the next cmdlet.
ForEach-Object
is like your foreach loops in other languages, with its own important flavour. In fact, PowerShell also has a foreach loop of its own! These may be easily confused but are operationally quite different. The main visual difference is that the foreach loop cannot be used in a pipeline, but ForEach-Object can. The latter, ForEach-Object, is a cmdlet (foreach is not) and can be used for transforming the current pipeline or for running a segment of code against the pipeline. It is really the most flexible cmdlet there is.
The best way to think about it is that it is the body of a loop, where the current element, $_, is coming from the pipeline and any output is passed onto the next cmdlet. To demonstrate:
# Transform
Get-Verb | ForEach-Object {"$($_.Verb) comes from the group $($_.Group)"}
# Retrieve Property
Get-Verb | ForEach-Object Verb
# Call Method
Get-Verb | ForEach-Object GetType
# Run Code
1..100 | ForEach-Object {
$increment = $_ + 1
$multiplied = $increment * 3
Write-Output $multiplied
}
Edit (Feb, 2023): thanks to #IkemKrueger for a missing }.
You have two things in there: filtering and iterating through a collection.
Filtering:
principle: Always use filtering left as much as possible. These two commands do the same thing, but the second one won't transmit a huge chunk of data through the pipe (or network):
Get-Process | where-Object {$_.Name -like 'chrome'} | Export-Csv
'c:\temp\processes.csv'
Get-Process -Name chrome | Export-Csv c:\temp\processes.csv
This is great when working with huge lists of computers or big files.
Many commandlets have their own filtering capabilities. Run get Get-Help get-process -full to see what they offer before piping.
iterating through collections:
Here you have 3 possibilities:
batch cmdlets is commandlet built in capability of passing a collection to another commandlet:
Get-Service -name BITS,Spooler,W32Time | Set-Service -startuptype
Automatic
WMI methods - WMI uses it's own way of doing the first one (different syntax)
gwmi win32_networkadapterconfiguration -filter "description like
'%intel%'" | EnableDHCP()
enumerating objects - iterating through the list:
Get-WmiObject Win32_Service -filter "name = 'BITS'" | ForEach-Object
-process { $_.change($null,$null,$null,$null,$null,$null,$null,"P#ssw0rd") }
Credits:
I found explanations that cleared the mess in my head around all these things in a book called : Learn Powershell in a month of lunches (chapters 9 and 13 in this case)

How to filter ProfileList with wildcards

I'm writing a script to help streamline the deletion of users profile list's on our Citrix servers. I cant figure out how to filter the ProfileImagePath by wildcards.
On our Windows 2008 servers in Regedit I can search and sort the profile list of users cia its Profileimagepath but this gets all the users, I cant seem to extend that filter to only bring back wildcard entries.
set-location | 'HKLM:\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList' |% {Get-ItemProperty $_.pspath } | Select
profileImagePath=username*
What I am getting is the whole list of user profiles, or if I put the filter =username* entry I get an error, What I wanted to get was just the results of the wildcard, Ideally just the usernames I put in the wildcard are what I want returned.
The code you posted is utterly broken. Please go find a PowerShell tutorial before proceeding any further.
Set-Location does not produce output, and even if it did, piping cmdlet output into a string wouldn't do anything at all.
You must not wrap statements at arbitrary places like you do in your sample code.
The wrapped registry path string will preserve the embedded newline (thus not matching what you want it to match).
Putting the argument of the Select-Object statement on the next line without escaping the newline will run Select-Object without arguments and the arguments as a separate statement (which most likely will throw an error because the arguments aren't a valid statement by themselves).
= is an assignment operator in PowerShell. Comparison operators are -eq (equality), -like (wildcard matches), and -match (regular expression matches).
You need Where-Object for filtering pipelined objects, not Select-Object. The latter is for selecting object properties.
You propbably meant to do something like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath | Where-Object {
(Get-ItemProperty $_.PSPath).ProfileImagePath -like "*${username}*"
}
or (simpler) like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath |
Get-ItemProperty |
Where-Object { $_.ProfileImagePath -like "*${username}*" }

How to pipe objects to a specific parameter

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.

is get-childItem's new -file parameter fast like -filter or slow like -include?

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.