Determine if parameter is regular expression? - powershell

I have created a Powershell routine for setting mp3 tags on songs, where I'd like some of my parameters to act as either a regular expession or a "simple" string. To be specific, if the parameter can be said to work as a regular expression, the function should try to use this for retrieving its value; if it can't, it should simply use that value.
I've just browsed Parameter sets, and don't think this would suit me since I want to be flexible with the parameter handling; i.e. I'd like several parameters to act this way independently. But maybe I'm wrong in this? Anyway, help would be appreciated.

You don't really need the try/catch if you use:
IF ($string -as [regex])
If the cast is successful it will return the regex, if not it will return $null, so used as a boolean test in the IF, it will be $true if it is a valid regex, and $false if it is not.
That being said, the I'd agree with Joey that you should settle on a single match type (either wildcard or regex) and stick with that. There's too much potential for unintended consequences in trying to determine if a regex metacharacter was intended to be match literally or not.

You can try converting the string to a regular expression and look for failures. If there is an exception, just use it as string:
$isParamRegex = $(try { $null = [regex]$Param; $true } catch { $false })
As for the parameter type, just make it a string and document it appropriately.
However, I'd say you might want to go a different route there:
Either make the argument always a regex, to avoid surprises with metacharacters.
Or make it a pattern for -like instead of -match which is a bit more predictable for users (imho).
In both cases provide a LiteralParam argument, akin to LiteralPath to just pass things as plain strings which are handled as such.

Related

If -match is case-insensitive, why do we need -imatch?

It seems redundant to provide -match and -imatch if -match is already case-insensitive. Is there any difference between them?
To elaborate on Doug Maurer's comment:
The i-prefixed variants of PowerShell operators that (also) operate on strings are never necessary. In fact, they are simply aliases of their non-prefixed forms, so that -imatch is the same as -match, for instance, and - with string input - always acts case-insensitively, as PowerShell generally does.
These variants exist for symmetry with the c-prefixed operator variants, which explicitly request case-sensitive operation (with string input).
In other words: you can use the i-prefixed variants to make it explicit that a given operation is case-insensitive.
However, to someone familiar with PowerShell's fundamentally case-insensitive nature, that isn't necessary - and that's probably why you rarely see the i-prefixed variants in practice.

Powershell splatting: pass ErrorAction = Ignore in hash table

Here's a script to list directories / files passed on the command line -- recursively or not:
param( [switch] $r )
#gci_args = #{
Recurse = $r
ErrorAction = Ignore
}
$args | gci #gci_args
Now this does not work because Ignore is interpreted as a literal. What's the canonical way to pass an ErrorAction?
I see that both "Ignore" and (in PS7) { Ignore } work as values, but neither seems to make a difference for my use case (bad file name created under Linux, which stops PS5 regardless of the ErrorAction, but does not bother PS7 at all). So I'm not even sure the parameter has any effect.
because Ignore is interpreted as a literal
No, Ignore is interpreted as a command to execute, because it is parsed in argument mode (command invocation, like a shell) rather than in expression mode (like a traditional programming language) - see this answer for more information.
While using a [System.Management.Automation.ActionPreference] enumeration value explicitly, as in filimonic's helpful answer, is definitely an option, you can take advantage of the fact that PowerShell automatically converts back and forth between enum values and their symbolic string representations.
Therefore, you can use string 'Ignore' as a more convenient alternative to [System.Management.Automation.ActionPreference]::Ignore:[1]
$gci_args = #{
# ...
ErrorAction = 'Ignore'
}
Note that it is the quoting ('...') that signals to PowerShell that expression-mode parsing should be used, i.e. that the token is a string literal rather than a command.
Also note that -ErrorAction only operates on non-terminating errors (which are the typical kind, however) - see this answer for more information.
As for discovery of the permissible -ErrorAction values:
The conceptual about_CommonParameters help topic covers all common parameters, of which -ErrorAction is one.
Many common parameters have corresponding preference variables (which accept the same values), covered in about_Preference_Variables, which allow you to preset common parameters.
Interactively, you can use tab-completion to see the permissible values (as unquoted symbolic names, which you simply need to wrap in quotes); e.g.:
# Pressing the Tab key repeatedly where indicated
# cycles through the acceptable arguments.
Get-ChildItem -ErrorAction <tab>
[1] Note that using a string does not mean giving up type safety, if the context unambiguously calls for a specific enum type, such as in this case. Validation only happens at runtime either way, given that PowerShell is an interpreted language.
However, it is possible for a PowerShell-aware editor - such as Visual Studio Code with the PowerShell extension - to flag incorrect values at design time. As of version 2020.6.0, however, that does not yet appear to be the case. Fortunately, however, tab-completion and IntelliSense work as expected, so the problem may not arise.
That said, as zett42 points out, in the context of defining a hashtable entry for latter splatting the expected type is not (yet) known, so explicit use of [System.Management.Automation.ActionPreference] does have advantages: (a) IntelliSense in the editor can guide you, and (b) - assuming that Set-StrictMode -Version 2 or higher is in effect - an invalid value will we reported earlier at runtime, namely at the point of assignment, which makes troubleshooting easier. As of PowerShell 7.1, a caveat regarding Set-StrictMode -Version 2 or higher is that you will not be able to use the intrinsic (PowerShell-supplied) .Count property on objects that don't have it type-natively, due to the bug described in GitHub issue #2798.
I think the best way is to use native type.
$ErrorActionPreference.GetType().FullName # System.Management.Automation.ActionPreference
So, use
$gci_args = #{
Recurse = $r
ErrorAction = [System.Management.Automation.ActionPreference]::Ignore
}

Do chained powershell replace commands execute one after the other?

This would likely be a non-issue with expert regex comprehension. And only matters because I am running multiple chained replace commands that affect some of the same text in a text file. I also imagine partitioning the txt files based on how delimiter words --that are requiring multiple replaces-- are used, before replace, would help. With that said basic structural knowledge of powershell is useful and I have not found many great resources (open to suggestions!).
The question: Do chained powershell replace commands execute one after the other?
-replace "hello:","hello " `
-replace "hello ","hello:"
} | out-file ...
Would this silly example above yield hello:'s where there were initially hello:'s?
From working through some projects I gather that the above works most of the time. Yet there always seem to be some edge cases. Is this another aspect of the script or is the order that chained commands (decent number of them) execute in never variable?
What you have there are operators, not commands.
I say that not to be pedantic, but because "command" has a specific meaning in PowerShell (it is a general name encompassing functions, cmdlets, aliases, applications, filters, configurations (this is a DSC construct), workflows, and scripts), and because the way they can be used together is different.
Most operators are reserved words that begin with - (but other things count as operators, like casting), and you can indeed use them chained together. They also execute in order.
I need to clarify; they don't necessarily execute in the order given when you mix operators. Multiple of the same operator will because they all have the same precedence, but you should check about_Operator_Precedence to see the order that will be used when you combine them.
Note that some operators can "short-circuit" (which may sound like a malfunction, but it isn't), that is the result of certain boolean operators will not evaluate later operations if the boolean result can not change.
For example:
$true -or $false
In this example, the $false part of the expression will never actually be evaluated. This is important if the next part of the expression is complex or even invalid. Consider these:
$true -or $(throw)
$false -or $(throw)
The first will return $true because (presumably) nothing in the coming expression could make it $false.
The second line must evaluate the second expression, and in doing so it throws an exception, halting the program.
So, aside from that aside, yes, you can continue to chain your operators. You also don't need a line continuation character (backtick `) at the end of the line if the operator itself is at the end. More useful with boolean operators:
$a -and
$b -or
$c -xor
$false
A little awkward with something like replace:
'apple' -replace
'p',
'z'
Regarding this:
And only matters because I am running multiple chained replace
commands that affect some of the same text in a text file.
These operators aren't touching anything in a file, they are working with data in memory, as literals or variables in your script (what you do with it then, like writing to a file is your business).
Further, even then it doesn't change any values already in variables, it returns new ones, which you may assign to a variable or use in any other way.
$var = 'apple'
$var -replace 'p','Z'
$var
The value of the replacement will be returned, but nothing was done with it so it went out to the console. Then you can see that $var was not modified at all, as opposed to:
$var = 'apple'
$var = $var -replace 'p','Z'
$var
Where the value of $var was overwritten.
If there are edge cases, it's likely to be a misunderstanding of something in the sequence of events (an incorrect regular expression, not assigning or using a value, incorrect logic, etc.), as the order of operations will be consistent. If you have any such edge cases, please post them!

Does PowerShell splatting support inline hashtable?

I want to use splatting syntax, but I want to do it inline
So instead of:
$p = #{Path = '.'}
ls #p
I want to do
ls ##{Path = '.'}
but this is a syntax error.
Is there anyway to splat a hashtable without having to write a separate variable?
Why do I want to do this? I prefer the hashtable syntax for defining alot of parameters (like 4 or more). But I'd rather not define a variable, I just want to have the cmdlet I'm calling at the "top" of the hashtable definition.
The splat operator will look for a variable whose name matches the characters after the splat sign, it doesn't try to resolve the characters after the sign as a statement to get the value. You cant do it inline. Link to TechNet.
As Francois mentioned, this is not possible yet, however, there are some discussions taking place around adding expanded splatting functionality (including this feature specifically) in the PowerShell RFC repository on GitHub:
RFC: https://github.com/PowerShell/PowerShell-RFC/blob/master/1-Draft/RFC0002-Generalized-Splatting.md
Discussion: https://github.com/PowerShell/PowerShell-RFC/issues/6

Formatting a Powershell string containing hashtable values

The answer to this is likely to be trivial, but I have spent half an hour and still can't work it out.
Assume I have a the following hashtable:
$hash = #{face='Off';}
What I have tried to do, is output the value of "face" along side some other string elements.
This works:
Write-Host Face $hash['face']
=> Face Off
However, this doesn't:
Write-Host Face/$hash['face']
=> Face/System.Collections.Hashtable[face]
Somehow the lack of a space has affected the operator precedence - it is now evaluating $hash as a string, the concatenating [face] afterwards.
Trying to solve this precedence problem, I tried:
Write-Host Face/($hash['face'])
=> Face/ Off
I now have an extra space I don't want.
This works, but I don't want the extra line just to reassign:
$hashvalue = $hash['face']
write-host Face/$hashvalue
=> Face/Off
Any idea how to get this working as a one-liner?
Sure, use a subexpression:
Write-Host Face/$($hash['face'])
Generally, I'd just use a string, if I need precise control over whitespace with Write-Host:
Write-Host "Face/$($hash['face'])"
Yes, in this case you need a subexpression again, but more because you simply can't include an expression like $foo['bar'] in a string otherwise ;-)
By the way, $hash.face works just as well with much less visual clutter.
In such cases, and more so if there are more variables involved, I prefer using the string formatting. While in this case you can live with the $(..), be aware that string formatting will remove lot of doubt, cruft and improves readability:
write-host ("Face/{0}" -f $hash['face'])
In addition to using sub expressions as Joey showed you can:
Use string formatting:
Write-Host ('Face/{0}' -f $hash.face)
This will stick the value of face key in the place of {0}
Use string concatenation:
Write-Host ('Face/' + $hash.face)
Both of those require an expression to be evaluated which outputs a string which is used as Write-Host's Object parameter.
Another option is to insert your slash with the -Separator option of Write-Host:
$hash = #{Face="off"}
Write-Host ("Face",$hash.Face) -Separator '/'