I am using PowerShell to compare two strings that have an ampersand (&) in them (i.e. the string "Policies & Procedures").
No matter what I try, I cannot get these strings to match. I have tried trimmed the strings to get rid of an extra white spaces. I have tried wrapping the the string in both single and double quotes (and a combination of both):
"Policies & Procedures"
'Policies & Procedures'
"'Policies & Procedures'"
The code I am using to compare the strings is:
if ($term1 -eq $term2) {
do something
}
Inspecting the strings visually - they are identical, however the if statement never evaluates to true. Is there a way to compare these two strings so that it does evaluate to true?
EDIT
The context in which I am doing this string compare is looking for a term name in a taxonomy for a SharePoint site. Here is the code I am using:
function getTerm($termName) {
foreach($term in $global:termset.Terms) {
$termTrimmed = $term.Name.trim()
Write-Host "term name = $termTrimmed" -foregroundcolor cyan
if ($termTrimmed -eq $termName) {
return $term
}
}
return null
}
I have printed both term.Name and termName to the screen and they are identical. If there is no ampersand in the string, this function works. If there is an ampersand this function fails. This is how I know the ampersand is the problem.
This is a known quirk:
There are two types of ampersands that you need to be aware of when
playing with SharePoint Taxonomy
Our favorite and most loved
& ASCII Number: 38
And the impostor
& ASCII Number: 65286
After reading this article by Nick Hobbs, it became apparent
that when you create a term it replaces the 38 ampersand with a
65286 ampersand.
This then becomes a problem if you want to do a comparison with your
original source (spreadsheet, database, etc) as they are no longer the
same.
As detailed in Nick’s article, you can use the
TaxonomyItem.NormalizeName method to create a "Taxonomy" version of
your string for comparison:
Try this (not tested on real SharePoint):
function getTerm($termName)
{
foreach($term in $global:termset.Terms) {
$termNormalized = [Microsoft.SharePoint.Taxonomy.TaxonomyItem]::NormalizeName($term.Name)
if ($termNormalized -eq $termName) {
return $term
}
}
return null
}
After converting both strings to char arrays and comparing the unicode value of the ampersands the problem is revealed. The ampersand used in the search string has a value of 38 while the ampersand returned from the SharePoint term store has a value of 65286 (called a full ampersand although looks identical to a regular ampersand on screen).
The solution was to write my own string comparison function and take into account the differences in the ampersand values. Here is the code:
function getTerm($termName) {
$searchChars = $termName.toCharArray()
$size = $searchChars.Count;
foreach($term in $global:termset.Terms) {
$match = $True
$chars = $term.Name.trim().toCharArray()
if ($size -eq $chars.Count) {
for ($i = 0; $i -lt $size; $i++) {
if ($searchChars[$i] -ne $chars[$i]) {
# handle the difference between a normal ampersand and a full width ampersand
$charCode1 = [int] $searchChars[$i]
$charCode2 = [int] $chars[$i]
if ((($charCode1 -eq 38) -or ($charCode1 -eq 65286 )) -and (($charCode2 -eq 38) -or ($charCode2 -eq 65286 ))) {
continue
} else {
$match = $False
break
}
}
}
} else {
$match = $False
}
if ($match -eq $True) {
return $term
}
}
return $null
}
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
I have a requirement at work to check several registry key values and I want to automate this process using PowerShell. One of the registry keys that I check has 3 values and I am not able to successfully check it using my PowerShell script.
I do not believe I am correctly loading the $value variable so that it can be compared to the value of $path.
$path = (Get-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer).NullSessionPipe
$value = “netlogon samr lsarpc”
if ($path -ne $value) {
Write-Host “Value is incorrect or missing.”
} else {
Write–Host “Config is correct.”
}
I expect the output to be
Config is correct.
Instead I get
Value is incorrect or missing.
Using Regedit I can see that the key has the correct values.
NullSessionPipe apparently is a REG_MULTI_SZ, meaning that the data is returned as an array of strings. You could do "$path" -ne $value to mangle the array into a flat string, but that would imply that the order of the substrings is identical in both strings. A better approach is to compare arrays via Compare-Object.
$path = (Get-ItemProperty HKLM:\...).NullSessionPipe
$value = 'netlogon', 'samr', 'lsarpc'
if (Compare-Object $path $value) {
'arrays differ'
} else {
'arrays are equal'
}
#Ansgar, After some experimenting I was able to get the script to work. I deleted the blank spaces and added a carriage return after netlogon and samr. It works perfectly!!!
$path = (Get-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer).NullSessionPipe
$value = 'netlogon',
' samr',
' lsarpc'
if (Compare-Object $path $value) {
'arrays differ'
} else {
'arrays are equal'
}
I want to check the name (for example azure vnet) provided in script to validate for azure vnet naming convention (Like it should not have any special character and length 2-64)
I am using below code but it is not working if string $name has special character in it.
It is working only for 0-9 and a-z.
$name = "zzz"
$name -cmatch "^[0-9a-z]*$"
Need a code to check a string which has for special character.
If it has special character it should return true.
Try :
$name = "hello#"
if($name -match '[^a-zA-Z0-9]')
{
Write-Host "special character found"
}
else
{
Write-Host "special character not found"
}
Try this one, if there is a special character in name, it will return true.
$name = "_"
$name -notmatch "[0-9a-zA-Z]"
my $line ="Corner:Default,Output:fall_delay_slew_1,Mean=34.97p,Std- dev=1.767p,Min=30.02p,Max=39.71p"; #added semicolon
my $my_value="COND = Mean > 3"; #this has come from the parsed file.
$my_value =~ m/(\w+)\s*(.)\s*(\d+)/;
my $cond=$1;
my $sign=$2;
my $value=$3;
print "DEBUG:cond is $cond and sign $sign and value $value \n";
if ( $line =~ m/$cond=(.*?),/) {
if ( "$value $sign $1" ) {
print "$value is $sign than $1\n";
} else {
print "actual value is less\n";
}
}
If you see in the above if statement always evaluates to true.
How can I solve this kind of problem i.e $sign = "<" (could be any operator)
but when I want to compare it with $value I want it to function as an
operator and not as a string.
What you're willing to do (executing a string as code) can be done with
eval. That doesn't mean it is the most appropriate way of doing
it though. Do it only if you guarantee your input safety and check for
it.
A better way would be checking the operator your self and determining
how to proceed. If you use a recent Perl version, the given-when
feature can be handy to do this:
use feature 'switch'; # not needed if you already use 5.010 or greater
given ($sign) {
when ('<') { say "$cond less than $value" }
when ('>') { say "$cond greater than $value" }
default {
warn "unrecognized operator `$sign'\n";
# decide what to do
}
}
So I'm playing around with Compare-Object, and it works fine for comparing files. But what about just strings? Is there a way to find the difference between strings? CompareTo() is good about reporting that there is a difference, but not what the difference is. For example:
PS:> $a = "PowerShell rocks"
PS:> $b = "Powershell rocks"
PS:> $a.CompareTo($b)
1
PS:> Compare-Object -ReferenceObject $a -DifferenceObject $b
PS:>
Nothing returned.
Any way to let me know about the actual difference between the strings, not just that there is a difference?
Perhaps something like this:
function Compare-String {
param(
[String] $string1,
[String] $string2
)
if ( $string1 -ceq $string2 ) {
return -1
}
for ( $i = 0; $i -lt $string1.Length; $i++ ) {
if ( $string1[$i] -cne $string2[$i] ) {
return $i
}
}
return $string1.Length
}
The function returns -1 if the two strings are equal or the position of the first difference between the two strings. If you want case-insensitive comparisons, you would need to use -eq instead of -ceq and -ne instead of -cne.