Passing a function to Powershell's (replace) function - powershell

I want to pass a function call(which returns a string) as a replacement string to Powershell's replace function such that each match found is replaced with a different string.
Something like -
$global_counter = 0
Function callback()
{
$global_counter += 1
return "string" + $global_counter
}
$mystring -replace "match", callback()
Python allows this through 're' module's 'sub' function which accepts a callback function as input. Looking for something similar

Perhaps you are looking for Regex.Replace Method (String, MatchEvaluator). In PowerShell a script block can be used as MatchEvaluator. Inside this script block $args[0] is the current match.
$global_counter = 0
$callback = {
$global_counter += 1
"string-$($args[0])-" + $global_counter
}
$re = [regex]"match"
$re.Replace('zzz match match xxx', $callback)
Output:
zzz string-match-1 string-match-2 xxx

PowerShell does not (yet?) have support for passing a script block to the -replace operator. The only option here is to use [Regex]::Replace directly:
[Regex]::Replace($mystring, 'match', {callback})

Related

powershell regex replace using variable parameters when calling replace

I wondering how to make the following powershell code work with regular expressions. For example I want to call it like this:
rename_file -OneFile proj1_file.txt -Replace0 "^proj1_" -Replace1 "mynewproj_"
The problem is that as soon as I add the regular expression caret (^) my function stops matching for the replace function... If I remove the caret anchor the code works... but is dangerous because its not anchored to the front of the string... I really want the regular expressions chracters to work when passing the string in to this function...
Here's my powershell code...
$script:TotalRename = 0
function rename_file {
param(
[string]$OneFile,
[string]$Replace0,
[string]$Replace1
)
#$NewName = $OneFile.Replace($Replace0, $Replace1)
$NewName = $OneFile -replace $Replace0, $Replace1
if ($NewName -eq $OneFile) {
write-host "NoChange $OneFile"
return
}
$script:TotalRename = $script:TotalRename + 1
write-host "rename $OneFile => $NewName"
}

how to use hashtable to pass parameters to a powershell cmd that uses double dashes before the parameters

i m writing a script that makes use of a cmdlet from a console app, let's call the cmdlet as cmdA. I have no control over implementation of cmdA. cmdA takes parameters with a double dash (cmdA --param1 value1 --param2 value2 --param3 value3 --param4 value4 --param5 value5)
Now param2, param3, param4 and param5 are optional. The user of my script may or may not provide values for these optional parameters. In the script, i formed a hashtable using the parameters for which the user provided values. Let's call it paramHashtable.
I am executing cmdA #paramHashtable but this is not working. I think it has to do something with double dashes because this approach works fine with a cmd that takes parameters with single dash
What would be an efficient way to pass parameters to the cmdA (an inefficient way would be to use many if blocks to check which values were provided and make calls accordingly)
edited*
This is how i m creating the hashtable :-
$optionalParameters = #{}
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
$requiredParameters = #{"sqlConnectionStrings " = "$SqlConnectionStrings"}
$parameters = $requiredParameters + $optionalParameters
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
$parametersList = #{}
if($outputFolder -ne "")
{
$parametersList["outputFolder "] = $outputFolder
}
if($perfQueryIntervalInSec -ne "")
{
$parametersList["perfQueryIntervalInSec "] = $perfQueryIntervalInSec
}
if($staticQueryIntervalInSec -ne "")
{
$parametersList["staticQueryIntervalInSec "] = $staticQueryIntervalInSec
}
if($numberOfIterations -ne "")
{
$parametersList["numberOfIterations "] = $numberOfIterations
}
return $parametersList
}
This is how i m calling it :-
& $ExePath actionName #parameters
The $ExePath has the path of the program to be executed
The actionName takes parameters like this:-
actionName --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
Can splatting with hash table work on cmdlets / functions where it's parameter have dashes?
It may work, but it is definitely not a good idea to have parameter names with dashes as this will result in a function / cmdlet where named parameters cannot be used, PowerShell binds the arguments positionally! (thanks mklement0 for pointing this out):
function Test-Splatting {
param(${-param1}, ${-param2})
"${-param1} ${-param2}"
}
$param = #{ '--param1' = 'hello'; '--param2' = 'world' }
Test-Splatting #param # => hello world
Example of what was mentioned before using the same function above:
# --param1 is bound positionally and not interpreted as a parameter:
Test-Splatting --param1 hello # => --param1 hello
As for an external programs, the linked answer in comments explains very well the approach you could take using a wrapper function and the use of the automatic variable $args:
function myfunc {
$binaryPath = 'path/to/file.exe'
& $binaryPath actionName $args
# or #args we can't be sure until testing
}
myfunc --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
As noted in the comments, because you are calling an external program, you should use array-based splatting or simply arrays directly to pass programmatically constructed arguments.
By contrast, hashtable-based splatting is usually only helpful when calling PowerShell commands (while it technically works with external programs too, the resulting parameter format (e.g. -foo:bar or -foo:"bar none") is unusual and understood by few external programs)
Note that the parameter names and values must be passed as separate array elements and the elements representing parameter names must include the - or -- prefix; e.g., consecutive array elements
'--sqlConnectionStrings' (the name) and
'Data Source=Server1' (the value).
PowerShell then constructs a command line for invocation of the external program behind the scenes, space-separating the array elements and double-quoting elements with embedded spaces on demand; the above example turns into the following:
--sqlConnectionStrings "Data Source=Server1"
Note that it is up to the target program to parse the single string that is its command line in a way that recognizes parameter name-value pairs.
# Get the array of *optional* parameter names and values from your helper function.
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
# Declare the *required* parameter(s) as an array.
$requiredParameters = '--sqlConnectionStrings', $SqlConnectionStrings
# Pass the arrays as-is to the external program.
# (Empty arrays are effectively ignored.)
& $ExePath actionName $requiredParameters $optionalParmeters
Your ParameterSelection function can be simplified as follows:
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
# Loop over all bound parameters.
$PSBoundParameters.GetEnumerator() | ForEach-Object {
# Emit the parameter name prefixed with '--' and the value.
# PowerShell automatically collects the output strings in an array
# when you assign a call to this function to a variable.
'--' + $_.Key
$_.Value
}
}

powershell: How to create and access static variables in a powershell class?

How to create a powershell class that has static variables?
class ex1 {
static [int]$count = 0
ex1() {
[ex1]::count = [ex1]::count + 1
write-host [ex1]::count
}
}
$ex1 = [ex1]::new()
$ex2 = [ex1]::new()
$ex3 = [ex1]::new()
exit 1
I tried this but all it does it prints:
[ex1]::new()
[ex1]::new()
[ex1]::new()
instead of incrementing the count to count the number of objects created in the static integer.
Apart from the constructor ex1() {..}, you need to add a method that actually returns the value of the static property Count:
class ex1 {
[int]static $count = 0
ex1() {
# constructor increments the static Count property
[ex1]::count++
}
[int]GetCount() {
# simply return the current value of Count
return [ex1]::count
}
}
$ex1 = [ex1]::new().GetCount()
$ex2 = [ex1]::new().GetCount()
$ex3 = [ex1]::new().GetCount()
$ex1, $ex2, $ex3 # --> resp. 1, 2, 3
Simple solution: Enclose static variable references like [ex1]::count in parentheses, whenever you want to pass them as an argument to another command like Write-Host:
Write-Host ([ex1]::count)
Why is that necessary when simple variables like $someVar don't require parentheses? For the same reason that Write-Host 2+2 doesn't print 4 but literally 2+2: PowerShells often counterintuitive argument parsing mode.
PowerShell switches from normal expression parsing mode to argument parsing mode, whenever it sees a call to a command (compare with a call to a .NET function, which doesn't change parsing mode). This mode follows its own rules, e. g. dollar sign followed by variable name causes PS to resolve the variable - as expected. Surprisingly there isn't a rule that covers static variable references, so PowerShell falls back to interpreting the argument as literal string.
Enclosing [ex1]::count in parentheses fixes the problem by forcing the parser to leave argument mode and parse an expression.
Full code sample with fix:
class ex1 {
static [int]$count = 0
ex1() {
[ex1]::count = [ex1]::count + 1
# Argument parsing mode -> wrap static variable reference in parentheses
write-host ([ex1]::count)
}
}
$ex1 = [ex1]::new()
$ex2 = [ex1]::new()
$ex3 = [ex1]::new()
[ex1]::count # Expression parsing mode -> no parentheses needed!
Note: The added last line is an example for PowerShells implicit output feature. By just naming a variable on its own line, PowerShell writes its value to standard output. As there is no command invocation involved, PowerShell is still in expression parsing mode, so we don't need to bracketize the expression.

I am trying to convert the "Return First recurring character in a string problem" from python to powershell

I have completed coding this same problem in python and was trying to generate a similar logic or atleast achieve a similar result in powershell.
Python Code-
def FRC(str):
h = {}
for ch in str:
if ch in h:
return ch
else:
h[ch] = 0
return '\0'
print(FRC("abcdedcba"))
I have tried a few possible codes and was able to only enumerate the array of characters to count their occurrences. Thank you for any suggestions.
Update1 - The code I have worked on is as follows:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #()
$teststring = $teststring.ToCharArray()
foreach ($letter in $teststring)
{
if($letter -contains $hash){
return $letter
}else {
$hash = $hash + $letter
}
return "\0"
}
}
get-duplicatechar("saahsahh")
You could use the (.Net) HashSet class for this, which Add method (besides adding the value,) returns true if the element is added to the HashSet<T> object and false if the element is already present.
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$str
)
$h = [System.Collections.Generic.HashSet[char]]::new()
foreach ($ch in $str.ToCharArray()) {
if(!$h.add($ch)) { return $ch }
}
}
Here's a working version using your code as base:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #{}
$CharArray = $teststring.ToCharArray()
foreach ($letter in $CharArray) {
if($letter -in $hash.Keys) {
$letter
break
}
else {
$hash[$letter] = $null
}
}
}
One problem is that you are strongly typing $teststring to be a string, so when you add a character array later PowerShell just converts it into a string and thus $teststring remains a string (try $teststring.GetType() after $teststring = $teststring.ToCharArray() to see this for yourself).
One way to solve this is to do what I did and use a different variable for the character array. You could also solve it by changing the variable to a character array directly by replacing [string]$teststring with [char[]]$teststring, that way any strings input to the function will be automatically cast as a character array.
The next mistake is using -contains where you need -in. The letter doesn't contain the array, you're looking for the letter in the array, just like you did in Python.
You can drop the return keyword entirely, PowerShell does not need it. Any output in your function will be output from the function automatically.
You also call your collection variable "hash", but you made an array. I changed it to be an actual hashtable, just like in your Python code. I also changed the way we add to it to more closely reflect what you did in Python. There are many ways to do this, this is just one. Notice we'll need to add ".Keys" in our if-statement as well so we check for keys matching our letter.
I think that's it, ask if anything is unclear.

$prompt = ($defaultValue,$prompt)[[bool]$prompt] - emulating a ternary conditional in PowerShell

I'm learning to write scripts with PowerShell, and I found this code that will help me with a project The example comes from Is there a one-liner for using default values with Read-Host?.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
I think I understand that $prompt = ($defaultValue,$prompt) is creating a two-element array and that the [bool] part is forcing the $prompt data type to Boolean, but I don’t understand what this third line of code does as a whole.
This is a common programming pattern:
if (user entered a price)
{
price = user entered value
}
else
{
price = default value
}
and because that is quite common, and also long winded, some languages have a special ternary operator to write all that code much more concisely and assign a variable to "this value or that value" in one move. e.g. in C# you can write:
price = (user entered a price) ? (user entered value) : (default value)
# var = IF [boolean test] ? THEN (x) ELSE (y)
and the ? assigns (x) if the test is true, and (y) if the test is false.
In Python, it's written:
price = (user entered value) if (user entered a price) else (default value)
And in PowerShell, it's written:
# you can't have a ternary operator in PowerShell, because reasons.
Yeah. No nice short code pattern allowed.
But what you can do, is abuse array-indexing (#('x', 'y')[0] is 'x' and #('x', 'y')[1] is 'y' and ) and write that ugly and confusing code-golf line:
$price = ($defaultValue,$userValue)[[bool]$UserEnteredPrice]
# var (x,y) is an array $array[ ] is array indexing
(0,1) are the array indexes of the two values
[bool]$UserEnteredPrice casts the 'test' part to a True/False value
[True/False] used as indexes into an array indexing makes no sense
so they implicitly cast to integers, and become 0/1
# so if the test is true, the $UserValue is assigned to $price, and if the test fails, the $DefaultValue is assigned to price.
And it behaves like a ternary operator, except it's confusing and ugly and in some situations it will trip you up if you're not careful by evaluating both array expressions regardless of which one is chosen (unlike real ? operators).
Edit: What I should really add is a PowerShell form I prefer - you can assign the result of an if test directly in PowerShell and do:
$price = if ($userValue) { $userValue } else { $DefaultValue }
# ->
$prompt = if ($prompt) { $prompt } else { $DefaultValue }
Casting $prompt to [bool] produces a value of $true or $false depending on whether the variable is empty ($null or empty string both become $false) or not (non-emtpy strings become $true).
[bool]'' → $false
[bool]'something' → $true
Using that boolean value in the index operator then implicitly casts the value to an integer where $false becomes 0 and $true becomes 1, hence selecting the first or second element of the array.
[int]$false → 0
[int]$true → 1
($defaultValue,$prompt)[0] → $defaultValue
($defaultValue,$prompt)[1] → $prompt
To complement the great answers given by Ansgar Wiechers and by TessellatingHeckler:
It would be great if PowerShell had operators for ternary conditionals and null-coalescing, such as follows (applied to the example in the question):
Update: PowerShell (Core) 7+ now does have these operators.
# Ternary conditional
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
$prompt = $prompt ? $prompt : $defaultValue
# Or, more succinctly, with null coalescence:
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
# (Note: This example assumes that $prompt will be $null in the default
# case, whereas the code in the question actually assigns the
# empty string to $prompt if the user just presses Enter.)
$prompt = $prompt ?? $defaultValue
Unfortunately, these expressive constructs are not part of Windows PowerShell, the legacy edition of PowerShell, which will see no new functionality.
Below are adapted versions of the functions from a 2006 PowerShell Team blog post with associated alias definitions, whose use then allows the following solution:
# Ternary conditional - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?: $prompt $prompt $defaultValue
# Or, more succinctly, with null coalescence - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?? $prompt $defaultValue
Source code:
Note that the actual functions are quite short; it is the comment-based help that makes this listing lengthy.
Set-Alias ?: Invoke-Ternary -Option AllScope
<#
.SYNOPSIS
Emulation of a ternary conditional operator.
.DESCRIPTION
An emulation of the still-missing-from-the-PS-language ternary conditional,
such as the C-style <predicate> ? <if-true> : <if-false>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?: Invoke-Ternary,
concise in-line conditionals become possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the tree operands.
A predicate script block is of necessity always evaluated, but a script block
passed to the true or false branch is only evaluated on demand.
.EXAMPLE
> Invoke-Ternary { 2 -lt 3 } 'yes' 'no'
Evaluates the predicate script block, which outputs $true, and therefore
selects and outputs the true-case expression, string 'yes'.
.EXAMPLE
> Invoke-Ternary $false { $global:foo = 'bar' } { Get-Date }
Outputs the result of executing Get-Date.
Note that the true-case script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-Ternary
{
[CmdletBinding()]
param($Predicate, $Then, $Otherwise = $null)
if ($(if ($Predicate -is [scriptblock]) { & $Predicate } else { $Predicate })) {
if ($Then -is [ScriptBlock]) { & $Then } else { $Then }
} else {
if ($Otherwise -is [ScriptBlock]) { & $Otherwise } else { $Otherwise }
}
}
Set-Alias ?? Invoke-NullCoalescence -Option AllScope
<#
.SYNOPSIS
Emulation of a null-coalescence operator.
.DESCRIPTION
An emulation of a null-coalescence operator such as the following:
<expr> ?? <alternative-expr-if-expr-is-null>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?? Invoke-NullCoalescence,
concise in-line null-coalescing becomes possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the two operands.
A first-operand script block is of necessity always evaluated, but a
second-operand script block is only evaluated on demand.
Note that only a true $null value in the first operand causes the second
operand to be returned.
.EXAMPLE
> Invoke-NullCoalescence $null '(empty)'
Since the first operand is $null, the second operand, string '(empty)', is
output.
.EXAMPLE
> Invoke-NullCoalescence '' { $global:foo = 'bar' }
Outputs the first operand, the empty string, because it is not $null.
Note that the second-operand script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-NullCoalescence
{
[CmdletBinding()]
param($Value, $Alternative)
if ($Value -is [scriptblock]) { $Value = & $Value }
if ($null -ne $Value) {
$Value
} else {
if ($Alternative -is [ScriptBlock]) { & $Alternative } else { $Alternative }
}
}