I've found out the format operator is working differently inside a function compared to a plain script.
Here's a simple example of what is working as expected:
[string]$name = 'Scripting Guy'
[string]$statement = 'PowerShell rocks'
$s = "The {0} thinks that {1}!" -f $name, $statement
write-host $s
producing:
The Scripting Guy thinks that PowerShell rocks!
While inside a function it does something different:
function myFunc( [string] $iname, [string] $istatement) {
$s = "The {0} thinks that {1}!" -f $iname, $istatement
write-host $s
}
[string]$name = 'Scripting Guy'
[string]$statement = 'PowerShell rocks'
myFunc($name, $statement)
produces:
The Scripting Guy PowerShell rocks thinks that !
I tried to play with it to find out what it's doing:
function myFunc( [string] $iname, [string] $istatement) {
$s = "The {0} thinks that {1}! {2} {3}" -f $iname, $istatement, "=====", $iname
write-host $s
}
[string]$name = 'Scripting Guy'
[string]$statement = 'PowerShell rocks'
myFunc($name, $statement)
This produces:
The Scripting Guy PowerShell rocks thinks that ! ===== Scripting Guy PowerShell rocks
So now I don't know what to think about this.
You should call the function as follows:
myFunc -iname "Scripting Guy" -istatement "Powershell Rocks!!"
or
myFunc $name $statement
The current method you're using passes a single array object that's why the elements get printed in succession
Related
Old thread
My question regards:
function GetStringBetweenTwoStrings($firstString, $secondString, $importPath){
#Get content from file
$file = Get-Content $importPath
#Regex pattern to compare two strings
$pattern = "$firstString(.*?)$secondString"
#Perform the opperation
$result = [regex]::Match($file,$pattern).Groups[1].Value
#Return result
return $result
}
GetStringBetweenTwoStrings -firstString "Lorem" -secondString "is" -importPath "C:\Temp\test.txt"
This is nice for only one -firstString and -secondString, but how to use this function to chronologically write multiple same strings in numbered TXT?
txt - file(with more sections of text):
Lorem
....
is
--> write to 001.txt
Lorem
....
is
--> write to 002.txt
and so forth....
And the structure of the section is preserved and is not in one line.
I hope someone can tell me that. Thanks.
The function you quote has several limitations (I've left feedback on the original answer), most notably only ever reporting one match.
Assuming an improved function named Select-StringBetween (see source code below), you can solve your problem as follows:
$index = #{ value = 0 }
Get-ChildItem C:\Temp\test.txt |
Select-StringBetween -Pattern 'Lorem', 'is' -Inclusive |
Set-Content -LiteralPath { '{0:000}.txt' -f ++$index.Value }
Select-StringBetween source code:
Note: The syntax is in part patterned after Select-String. After defining the function, run Select-StringBetween -? to see its syntax; the parameter names are hopefully self-explanatory.
function Select-StringBetween {
[CmdletBinding(DefaultParameterSetName='String')]
param(
[Parameter(Mandatory, Position=0)]
[ValidateCount(2, 2)]
[string[]] $Patterns,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='File')]
[Alias('PSPath')]
[string] $LiteralPath,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='String')]
[string] $InputObject,
[switch] $Inclusive,
[switch] $SimpleMatch,
[switch] $Trim
)
process {
if ($LiteralPath) {
$InputObject = Get-Content -ErrorAction Stop -Raw -LiteralPath $LiteralPath
}
if ($Inclusive) {
$regex = '(?s)(?:{0}).*?(?:{1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
else {
$regex = '(?s)(?<={0}).*?(?={1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
if ($Trim) {
[regex]::Matches(
$InputObject,
$regex
).Value.Trim()
}
else {
[regex]::Matches(
$InputObject,
$regex
).Value
}
}
}
Note that there's also a pending feature request on GitHub to add this functionality directly to Select-String - see GitHub issue #15136
Im wondering how you can construct a string on the fly as a parameter to a function?
Example
say I have a function like
function MyFunc
{
Param
(
[Parameter(mandatory=$true)] [string] $myString,
[Parameter(mandatory=$true)] [int] $myInt
)
Write-Host ("Param 1 is {0}" -f $myString)
Write-Host ("Param 2 is {0}" -f $myInt)
}
How can I call it whilst constructing the first string param on the fly e.g.
$myName = "Casper"
$myInt=7
MyFunc "Name is " + $myName $myInt
Ive tried putting {} around the first "bit" like
MyFunc {Name is " + $myName} $myInt
This then incorrectly prints out
Param 1 is "Name is "+$myName
Param 2 is 7
what I want it to print is
Param 1 is "Name is Casper"
Param 2 is 7
I know a better way of doing this would just be to set up the string first,
$pm1 = "Name is " + $myName
and call function
MyFunc $pm1 $myInt
but I am just interested to know how it can be done on the fly as it were. How can I construc the string and pass as first parameter on the function call? Hope thats clear.
Thanks
As a general rule of thumb, you can always nest any complex expression in a separate pipeline using the subexpression operator $(...) or grouping operator (...):
MyCommand $("complex",(Get-Something),"argument","expression" -join '-')
But in your particular case we don't need that - you just need to place the variable expression $myName inside the string literal and PowerShell will automatically evaluate and expand its value:
MyFunc "Name is $myName" $myInt
If the variable expression is to be followed by some characters that would otherwise make up a valid part of the variable path, use curly brackets {} as qualifiers:
MyFunc "Name is ${myName}" $myInt
I don't understand what is going on...
In the VerifyEmailSettings function, the $AdminEmailAddress is one of many parameters i can pass to the ps command i am using.
I want to be able to pass the paramater name, and value to other functions like below. However, when i pass this along, i get some odd results. As you can see in the results, trying to print the $SettingName in the VerifyEmailSettings function echos AdminEmailAddress admin#superuser.com Verified, Same instead of what i want... AdminEmailAddress Verified, Same The "admin#superuser.com is mixed in there somehow. Same happens with the $SetName in the SetEmailSettings functions.
Thanks in advance!!
Write-Host "Starting Script"
#Assigning Variables
$AdminEmailAddress = "admin#superuser.com"
$SmtpServer = "exchange.local"
$FromEmailAddress = "fsrm#omg.com"
If (GetInstallStatus){
Write-Host "FSRM Installed, Skipping Install"
Write-Host "Checking Email Settings"
VerifyEmailSettings([string]"AdminEmailAddress",[string]$AdminEmailAddress)
} else {
Write-Host "FSRM Not Installed, Installing"
Install-WindowsFeature –Name FS-Resource-Manager –IncludeManagementTools
If (GetInstallStatus){
Write-Host "FSRM Installed"
} else {
Write-Host "FSRM Error on Install, Halting"
#halt here
}
}
function GetInstallStatus {
$status = (Get-WindowsFeature -Name FS-Resource-Manager | ft Installed -autosize -hidetableheaders | out-string).trim();
return $status
}
function VerifyEmailSettings([string]$SettingName, [string]$SettingData) {
$Verify = (Get-FsrmSetting | Select-Object $SettingName | FT -autosize -hidetableheaders | Out-String).Trim()
If ($Verify -eq $SettingData) {
Write-Host $SettingName "Verified, Same"
SetEmailSettings([string]$SettingName, [string]$SettingData)
} Else {
Write-Host $SettingName "Wrong, Updating"
SetEmailSettings([string]$SettingName, [string]$SettingData)
}
}
function SetEmailSettings([string]$SetName, [string]$SetData) {
$SetName
#Set-FsrmSetting $SetName $SetData
}
Here is the results i get:
Starting Script
FSRM Installed, Skipping Install
Checking Email Settings
AdminEmailAddress admin#superuser.com Verified, Same
AdminEmailAddress admin#superuser.com
Do not call PowerShell functions with parentheses and commas
VerifyEmailSettings([string]"AdminEmailAddress",[string]$AdminEmailAddress)
What you're actually doing here is passing an array containing both values as the first argument and nothing for the second argument. That should be written like this:
VerifyEmailSettings "AdminEmailAddress" $AdminEmailAddress
Or
VerifyEmailSettings -SettingName "AdminEmailAddress" -SettingData $AdminEmailAddress
(there is no need to cast your strings as [string])
Use Strict Mode
What you've done is a common error in PowerShell, made more common by the fact that you do use parentheses and commas when calling methods on .Net objects. I still do this once in a while after years of using PowerShell.
You can set strict mode which actually catches this for you and warns you about it:
Set-StrictMode -Version 2.0
If i replace a string in another one using something simple like this
my $pet = "I have a dog.";
my $search = "dog";
my $replace = "cat";
$pet =~ s/$search/$replace/;
it works fine and i get "I have a cat." as expected.
But when i am using something more complex like the following it doesn't get replaced:
my $image_correction_hash = {};
$device = "my_device";
$correction_hash->{$device}->{'to_be_replaced'} = "174_4492_232313_7078721ec0.jpg";
# my json string
my $json = '[{"credits":[],"issue":174,"layout":"special_cover","text":[],"hide_overline":"","category":"Kunst","id":"174_4492","media_data":[{"thumbnail":"","data_is_cover":1,"subheadline":"","value":"174_4492.jpg","type":"image","headline":""},{"data_position":"left","thumbnail":"","subheadline":"","value":"174_4492_232302_3980b3da34.jpg","data_effect":"smear","type":"image","headline":""},{"data_position":"right","thumbnail":"","subheadline":"","value":"174_4492_232313_7078721ec0.jpg","data_effect":"smear","type":"image","headline":""}],"links":[],"textmarker":"","teaser":"","hide_headline":"","article_thumbnail":"174_4492_article_thumbnail.jpg","subheadline":"","gallery":[],"overline":"","headline":"Covertitel\n"}]';
print STDERR "JSON string before:" . $json . "\n";
foreach my $search ( keys %{$correction_hash->{$device}})
{
print STDERR "to be replaced:".$correction_hash->{$device}->{$search}.".\n";
# the replacement
$json =~ s/$search/XXXXX/g;
}
print STDERR "JSON string after:" . $json . "\n"; # no replacement occured - GRRR
Where is the error here?
You mixed up your variables.
Try this:
print STDERR "to be replaced:".$search.".\n";
It will print this: to be replaced:to_be_replaced.
So you can use this code:
my $pattern = $correction_hash->{$device}->{$search};
$json =~ s/$pattern/XXXXX/g;
On a side note, if your $pattern is not a regex, you should escape it using this code:
$json =~ s/\Q$pattern\E/XXXXX/g;
You're trying to use $search in your pattern replacement, not the actual pattern you want to replace. So you're trying to replace to_be_replaced with XXXXXXXX. Not 174_4492_232313_7078721ec0.jpg.
You probably want to add:
$replace_pattern = $correction_hash->{$device}->{$search};
$json =~ s/$replace_pattern/XXXXX/g;
Whatever you want to call it, I'm trying to figure out a way to take the contents of an existing string and evaluate them as a double-quoted string. For example, if I create the following strings:
$string = 'The $animal says "meow"'
$animal = 'cat'
Then, Write-Host $string would produce The $animal says "meow". How can I have $string re-evaluated, to output (or assign to a new variable) The cat says "meow"?
How annoying...the limitations on comments makes it very difficult (if it's even possible) to include code with backticks. Here's an unmangled version of the last two comments I made in response to zdan below:
----------
Actually, after thinking about it, I realized that it's not reasonable to expect The $animal says "meow" to be interpolated without escaping the double quotes, because if it were a double-quoted string to begin with, the evaluation would break if the double quotes weren't escaped. So I suppose the answer would be that it's a two step process:
$newstring = $string -replace '"', '`"'
iex "`"$string`""
One final comment for posterity: I experimented with ways of getting that all on one line, and almost anything that you'd think works breaks once you feed it to iex, but this one works:
iex ('"' + ($string -replace '"', '`"') + '"')
Probably the simplest way is
$ExecutionContext.InvokeCommand.ExpandString($var)
You could use Invoke-Expression to have your string reparsed - something like this:
$string = 'The $animal says `"meow`"'
$animal = 'cat'
Invoke-Expression "Write-Host `"$string`""
Note how you have to escape the double quotes (using a backtick) inside your string to avoid confusing the parser. This includes any double quotes in the original string.
Also note that the first command should be a command, if you need to use the resulting string, just pipe the output using write-output and assign that to a variable you can use later:
$result = Invoke-Expression "write-output `"$string`""
As noted in your comments, if you can't modify the creation of the string to escape the double quotes, you will have to do this yourself. You can also wrap this in a function to make it look a little clearer:
function Invoke-String($str) {
$escapedString = $str -replace '"', '`"'
Invoke-Expression "Write-Output `"$escapedString`""
}
So now it would look like this:
# ~> $string = 'The $animal says "meow"'
# ~> $animal = 'cat'
# ~> Invoke-String $string
The cat says "meow"
You can use the -f operator. This is the same as calling [String]::Format as far as I can determine.
PS C:\> $string = 'The {0} says "meow"'
PS C:\> $animal = 'cat'
PS C:\> Write-Host ($string -f $animal)
The cat says "meow"
This avoids the pitfalls associated with quote stripping (faced by ExpandString and Invoke-Expression) and arbitrary code execution (faced by Invoke-Expression).
I've tested that it is supported in version 2 and up; I am not completely certain it's present in PowerShell 1.
Edit: It turns out that string interpolation behavior is different depending on the version of PowerShell. I wrote a better version of the xs (Expand-String) cmdlet with unit tests to deal with that behavior over here on GitHub.
This solution is inspired by this answer about shortening calls to object methods while retaining context. You can put the following function in a utility module somewhere, and it still works when you call it from another module:
function xs
{
[CmdletBinding()]
param
(
# The string containing variables that will be expanded.
[parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true)]
[string]
$String
)
process
{
$escapedString = $String -replace '"','`"'
$code = "`$ExecutionContext.InvokeCommand.ExpandString(`"$escapedString`")"
[scriptblock]::create($code)
}
}
Then when you need to do delayed variable expansion, you use it like this:
$MyString = 'The $animal says $sound.'
...
$animal = 'fox'
...
$sound = 'simper'
&($MyString | xs)
&(xs $MyString)
PS> The fox says simper.
PS> The fox says simper.
$animal and $sound aren't expanded until the last two lines. This allows you to set up a $MyString up front and delay expansion until the variables have the values you want.
Invoke-Expression "`"$string`""