What does "-" do in Powershell - powershell

I'm new to powershell and I'm still trying to understand the syntax.
In a command like this
Get-ChildItem *.txt | Rename-Item -NewName { $_.name -Replace '\.txt$','.log' }
What does - actually do?
Sometimes it is there, sometimes not, and I just don't understand what purpose it has xD

The hyphen character in PowerShell has a variety of uses that are context specific. It is primarily used in cmdlet names, parameters, operators and of course as a character literal.
CMDLet names
It is commonly used as a verb-noun separator e.g. Get-ChildItem. This is an encouraged practice when making custom functions and cmdlets as well.
Parameters
Used to tell the parser that a word denotes a parameter e.g. Rename-Item -NewName. In the example it is -NewName
Operators
This is a broad section but you will see the hyphen denote operators like -replace in your code sample. It does not always have a keyword associated either which is the case with arithmetic operators (-) and assignment operators (-=). You will also see the hyphen with comparison, containment, pattern-matching/text and logical/bitwise operators.

In Powershell, All the parameters of a specific cmdlet has been defined to start with "-". It indicates the parameters are for the corresponding cmdlet.
All the cmdlets are functions that are written in Csharp or Powershell functions where they have defined the way to pass the argument of the parameters like:
Get-ChildItem *.txt | Rename-Item -NewName { $_.name -Replace '\.txt$','.log' }
Get-Childitem is the cmdlet which has a -include paramater and whose value is **.txt*. So even though you have not given the parameter name , powershell has the capability to identify certain parameters value by native.
So that is the reason it is having no issue.
Similarly when you are piping the output of the first cmdlet to the second one (which is Rename-item) , it has a parameter -NewName whose value has been passed as an output of the entire {$_.name -Replace '.txt$','.log' }
Hope it helps you.

-LiteralPath is what the meaning of using -. It's for paramters.
From your example, -NewName is treated as a parameter and -Replace is the operator
EDIT: As Peter rightly pointed out; amended the answer

Related

PowerShell unable to use two -Like comparison operators together

I m learning PowerShell and one of the task I did is to filter a Csv file records.
Based on this link: https://4sysops.com/archives/create-sort-and-filter-csv-files-in-powershell/ I tried something similar to:
Import-Csv -Path '.\sample.csv' | Select-Object EmailAddress,UniqueName,LastLoginDate | ? EmailAddress -like *gmail.com -Or ? EmailAddress -like *outlook.com | Export-Csv -Path $fileOut -NoTypeInformation
But the above gives me the error mentioned in the title.
Based on this link: https://www.computerperformance.co.uk/powershell/match/ I addressed the error by using Where-Object instead after the Select-Object line as follows:
Where-Object {$_.EmailAddress -Like "*gmail.com" -Or $_.EmailAddress -Like "*outlook.com"}
Why does the first example give me error but not the second example?
tl;dr
Both your commands use the Where-Object cmdlet; ? is simply a built-in alias for it.
However, your commands use different syntax forms: your first command uses the simpler and more concise, but feature-limited individual argument-based simplified syntax, whereas your second one uses the verbose, but fully featured script-block syntax - see next section.
Because you need to combine multiple -like operations, you must use script-block syntax - simplified syntax limits you to a single operation.
Regular, script block-based syntax:
Example:
# You're free to add additional expressions inside { ... }
Where-Object { $_.EmailAddress -like '*gmail.com' }
uses a single argument that is a script block ({ ... }), inside of which the condition to test is formulated based on the automatic $_ variable that represents the input object at hand.
This syntax:
Places no constraints on the complexity of the expression - the whole PowerShell language is at your disposal inside a script block.
However, it is somewhat verbose.
Simplified, multi-argument syntax:
Example:
# Equivalent of the above.
# Note the absence of { ... }, $_, and "..."
Where-Object EmailAddress -like *gmail.com
Simplified syntax is an alternative syntax that may be used with Where-Object as well as ForEach-Object, which:
as the name implies, is simpler and less verbose.
but is limited to a single conditional / operation based on a single property, or, in the case of method calls with ForEach-Object, the input object itself.
With simplified syntax the parts that make up a conditional / method call are passed as separate arguments, which therefore bind to distinct parameters that are specifically designed to work with this syntax:
Because separate arguments are used, there is no { ... } enclosure (no script block is used).
$_ need not be referenced, because its use is implied; e.g. EmailAddress is the equivalent of $_.EmailAddress in the script block-syntax.
A notable limitation as of PowerShell 7.2.x is that with Where-Object you cannot operate on the input object itself - you must specify a property. GitHub issue #8357 discusses overcoming this limitation in the future, but there hasn't been any activity in a long time.
As usual in argument-mode parsing, quoting around string values is optional, assuming they don't contain metacharacters such as spaces; e.g., *.gmail.com - without "..." or '...' - works with simplified syntax, whereas the expression-mode parsing inside the equivalent script block requires quoting, e.g. '*gmail.com'

Rename files & append Number in Powershell [duplicate]

I've never used powershell or any cmd line to try and rename files, nor so I really know much about script writing in general.
I've already had some success in renaming the files in question but am stuck on the last piece of the puzzle.
Original file names:
NEE100_N-20210812_082245.jpg
NEE101_E-20210812_083782.jpg
NEE102_W-20210812_084983.jpg
I successfully change those to AT-###-N-......jpg using:
Rename-Item -NewName {$_.name -replace "NEE\d\d\d_", "AT-112-"}
And this is what they looked like after:
AT-112-N-20210812_082245.jpg
AT-112-E-20210812_083782.jpg
AT-112-W-20210812_084983.jpg
Now however, I have a few files that look like this:
AT-112-NewImage-20210812_083782.jpg
AT-112-NewImage-20210812_093722.jpg
and I want to change them to:
AT-112-D1-20210812_083782.jpg
AT-112-D2-20210812_093722.jpg
...and so on.
I've tried a few things here to try and do that. Such as replacing "NewImage" with "D" and then using something like this (not exact, just an example):
$i = 1
Get-ChildItem *.jpg | %{Rename-Item $_ -NewName ('19981016_{0:D4}.jpg' -f $i++)}
But this did not work. I have seen scripts that use sequential numbering either added as a suffix or a prefix. But I can't figure out how to do this if what I want to have sequence numbering in the middle of the name.
Hopefully this make sense, if I need more elaboration, let me know. Thanks!
You need to use an expression (inside (...)) as your -replace substitution operand in order to incorporate a dynamic value, such as the sequence number in your case.
In order to use a variable that maintains state across multiple invocations of a delay-bind script block ({ ... }, the one being passed to the -NewName parameter in your first attempt), you need to create the variable in the caller's scope and explicitly reference it there:
This is necessary, because delay-bind script blocks run in a child scope, unfortunately,[1] so that any variables created inside the block go out of scope after every invocation.
Use Get-Variable to obtain a reference to a variable object in the caller's (parent) scope[2], and use its .Value property, as shown below.
$i = 1
Get-ChildItem *.jpg | Rename-Item -NewName {
$_.Name -replace '-NewImage-', ('-D{0}-' -f (Get-Variable i).Value++)
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note: The above solution is simple, but somewhat inefficient, due to the repeated Get-Variable calls - see this answer for more efficient alternatives.
[1] This contrasts with the behavior of script blocks passed to Where-Object and ForEach-Object. See GitHub issue #7157 for a discussion of this problematic discrepancy.
[2] Without a -Scope argument, if Get-Variable doesn't find a variable in the current scope, it looks for a variable in the ancestral scopes, starting with the parent scope - which in this case the caller's. You can make the call's intent more explicitly with -Scope 1, which starts the lookup from the parent scope.

Replace text in files within a folder PowerShell

I have a folder that contains files like 'goodthing 2007adsdfff.pdf', 'betterthing 2007adfdsw.pdf', and 'bestthing_2007fdsfad.pdf', I want to be able to rename each, eliminating all text including 2007 OR _2007 to the end of the string keeping .pdf and getting this result: 'goodthing.pdf' 'betterthing.pdf' 'bestthing.pdf' I've tried this with the "_2007", but haven't figured out a conditional to also handle the "2007". Any advice on how to accomplish this is greatly appreciated.
Get-ChildItem 'C:Temp\' -Name -Filter *.pdf | foreach { $_.Split("_2017")[0].substring(0)}
Try the following:
Get-ChildItem 'C:\Temp' -Name -Filter *.pdf |
Rename-Item -NewName { $_.Name -replace '[_ ][^.]+' } -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
The above uses Rename-Item with a delay-bind script block and the -replace operator as follows:
Regex [_ ][^.]+ matches everything from the first space or _ char. (character set [ _]) through to the following literal . char. ([^.]+ matches one or more chars. other than (^) than .) - that is, everything from the first / _ through to the filename extension (excluding the .).
Note: To guard against file names such as _2017.pdf matching (which would result in just .pdf as the new name), use the following regex instead: '(?<=.)[_ ][^.]+'
By not providing a replacement operand to -replace, what is matched is replace with the empty string and therefore effectively removed.
The net effect is that input files named
'goodthing 2007adsdfff.pdf', 'betterthing 2007adfdsw.pdf', 'bestthing_2007fdsfad.pdf'
are renamed to
'goodthing.pdf', 'betterthing.pdf', 'bestthing.pdf'
Without knowing the names of all the potential files, I can offer this solution that is 100%:
PS> $flist = ("goodthing 2007adsdfff.pdf","betterthing 2007adfdsw.pdf","bestthing_2007fdsfad.pdf")
PS> foreach ($f in $flist) {$nicename = ($f -replace "([\w\s]+)2007.*(\.\w+)", '$1$2') -replace "[\s_].","." ;$nicename}
goodthing.pdf
betterthing.pdf
bestthing.pdf
Two challenges:
the underscore is actually part of the \w character class. So the alternative to the above is to complicate the regex or try to assume that there will always be only one '_' before the 2007. Both seemed risky to me.
if there are spaces in filenames, there is no telling if you might encounter more than one. This solution removes only the one right before 2007.
The magic:
The -replace operator enables you to quickly capture text in () and re-use it in variables like $1$2. If you have more complex captures, you just have to figure out the order they are assigned.
Hope this helps.

How do I use square brackets in a wildcard pattern in PowerShell Get-ChildItem?

I want to list all files ending with some text in square brackets.
But neither Get-ChildItem *[* nor Get-ChildItem *`[* nor Get-ChildItem *``[* work.
How can I make this work without much ado (i.e. by creating variables, running additional commands through the pipe etc.)
The following, which includes one of the things you tried, should work, but currently[1] doesn't work due to a bug:
# SHOULD work, but CURRENTLY BROKEN:
Get-ChildItem *``[* # 1st ` is for string parsing, 2nd ` for wildcard escaping
Get-ChildItem "*``[*" # ditto, with double quotes
Get-ChildItem '*`[*' # single-quoted alternative, requires only 1 `
Note that the use of a (the first) positional argument implicitly binds to Get-ChildItem's -Path parameter.
The intent is for Get-ChildItem to see the following literal after argument parsing: *`[*, which correctly escapes [ with ` in order to treat it as a literal.
As an aside: unquoted *`[* is equivalent to double-quoted "*`[*", which results in literal *[*, because PowerShell's string parsing interprets the ` and effectively removes it.
Workarounds:
Instead of escaping the [ character, enclose it in [...], a character-set expression, which causes it to be matched literally:
Get-ChildItem *[[]* # OK
Interestingly, performing the filtering via -Include does not exhibit the bug:
Get-ChildItem * -Include '*`[*' # OK
Another option is to use -Filter instead of (implied) -Path, as demonstrated in Paxz's answer, but note that -Filter's wildcard language is not the same as PowerShell's (as supported by the -Path and -Include / -Exclude parameters); the -Filter argument is passed to the Windows API, whose wildcard language differs as follows:
It supports fewer constructs, notably no character sets or ranges ([...]).
It has legacy quirks - see this answer.
On the plus side, use of Filter, due to filtering at the source, performs better than letting PowerShell do the filtering via (implied) -Path or -Include.
Yet another option would be to add another layer of escaping, but that is ill-advised, because it will stop working once the bug is fixed:
# NOT RECOMMENDED: will stop working once the bug is fixed.
Get-ChildItem '*``[*'
[1] As of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.3
You have to use the -Filter Parameter correct.
When you don't specify the Parameter, like you did in your examples, it will assume you want to use the first Parameter (in this case -Path, Ref. Get-ChildItem Doc).
Try this instead:
Get-ChildItem -Filter "*`[*"
This found the file ad.a[s] for me.
You can also change the filter to this:
Get-ChildItem -Filter "*`[*`]"
to expand it for the closing bracket.

PowerShell "Period" operator, what does it do?

I've been looking online for a specific answer to better help me understand how this works. In PHP we use the " . " to concatenate strings. However in powershell I see things like this:
Dir | where {$_.extension -eq ".txt"} |
Rename-Item –NewName { $_.name –replace “.“,”-” }
I can see that the "Dir" command is piped to "Where" but, I don't understand what its defining a variable for using:
$_.extension
Is this a way of adding extra operators to a function?? I'm pretty confused. I'm getting better but, I need to know how exactly periods and the $_. work when using the cmdlets and what not.
Any help is appreciated.
Powershell has very good help files included that can answer many questions.
See:
get-help about_operators
and you will find that the dot is used as both a Property dereferencing operator and a scope operator, with explanations of the use of each.
Can also see this under about_operators on TechNet
It's the member access operator. $_ is a special variable (the loop variable in this case). Therefore, $_.extension accesses or invokes the property extension on $_.
DIR command is similar to Get-ChildItem command. The | is similar to foreach statement. The $_ sign indicates each element in foreach loop. In your case, the code should get all which have .txt extension from some location and then rename each of those elements due to { $_.name –replace “.“,”-” } rule