Powershell string manipulation and replace - string

I have a powershell string which can contain multiple email address, for example below is a exmaple that contains two email ids. In that i have two scenarios.
1) Scenario One where the #gmail.com is consistent
strEmail=john.roger#gmail.com,smith.david#gmail.com
2) Secnario second: where the #mail.com could be different
strEmail2 = john.roger#gmail.com,smith.david#outlook.com
I need to get rid of anything after # including it.
So for result for
scenario (1) will be: john.roger,smith.david
Scenario (2) will be: john.roger,smith.david
SO for Scenarion(1) i can use replace with "hardcoded" value of "#gmail.com", How about second secnario.
I am looking for some solution which will work for both scenarion... like something in Regex or any other way i don't know.

Splitting and joining would return the names on one line
Following
$strEmail = "john.roger#gmail.com,smith.david#outlook.com"
($strEmail -split "," | % {($_ -split "#")[0]}) -join ","
returns
john.roger,smith.david
Breakdown
$strEmail -split "," returns an array of two elements
[0] john.roger#gmail.com
[1] smith.david#outlook.com
% {($_ -split "#")[0]} loops over the array
and splits each item into an array of two elements
[0] john.roger
[1] gmail.com
[0] smith.david
[1] outlook.com
and returns the first element [0] from each array
- join "," joins each returned item into a new string

Both of these should work.
This will print each name on a new line:
$strEmail = "john.roger#gmail.com,smith.david#outlook.com"
$strEmail = $strEmail.Split(',') | Foreach {
$_.Substring(0, $_.IndexOf('#'))
}
$strEmail
This will give you the same output as you outlined above:
$strEmail = "john.roger#gmail.com,smith.david#outlook.com"
$strEmailFinal = ""
$strEmail = $strEmail.Split(',') | Foreach {
$n = $_.Substring(0, $_.IndexOf('#'))
$strEmailFinal = $strEmailFinal + $n + ","
}
$strEmailFinal.TrimEnd(',')

Another approach... Well, if you like RegEx of course
Clear-Host
$SomeEmailAddresses = #'
1) Scenario One where the #gmail.com is consistent
strEmail=john.roger#gmail.com,smith.david#gmail.com
2) Secnario second: where the #mail.com could be different
strEmail2 = john.roger#gmail.com,smith.david#outlook.com
'#
((((Select-String -InputObject $SomeEmailAddresses `
-Pattern '\w+#\w+\.\w+|\w+\.\w+#\w+\.\w+|\w+\.\w+#\w+\.\w+\.\w+' `
-AllMatches).Matches).Value) -replace '#.*') -join ','
Results
john.roger,smith.david,john.roger,smith.david
Just comment out or delete the -join for one per line

Related

Powershell partial string comparisson

I'm currently stuck on a specific comparisson problem. I have two CSV files which contain application names and I need to compare both csvs for matching names. Of course that would be easy if the applications were written the same ways in both csvs, but they're not.
Each csv has two columns but only the first column contains tha application names. In csv01 an app is called "Adobe Acrobat Reader DC Continuous MUI" while the same app in csv02 is called "Adobe Acrobat Reader DC v2022.002.20191". By looking at the files, I know both contain "Adobe Reader DC". But I'd like to automate th comparisson as the csvs contains hundreds of apps.
I initially thought I'd run a nested foreach loop, taking the first product in csv01 and comparing every app in csv02 to that product to see if I have a match. I did that by splitting the application names at each space character and came up with the following code:
# Define the first string
$Products01 = Import-CSV 'C:\Temp\ProductsList01.csv' -Delimiter ";"
# Define the second string
$Products02 = Import-CSV 'C:\Temp\ProductList02.csv' -Delimiter ";"
# Flag to track if all parts of string2 are contained within string1
$allPartsMatch = $true
# Create Hashtable for results
$MatchingApps = #{}
# Loop through each part of string2
foreach ($Product in $Products01.Product) {
Write-Host "==============================="
Write-Host "Searching for product: $Product"
Write-Host "==============================="
# Split the product name into parts
$ProductSplit = $Product -split " "
Write-Host "Split $Product into $ProductSplit"
foreach ($Application in $Products02.Column1) {
Write-Host "Getting comparisson app: $Application"
# Split the product name into parts
$ApplicationSplit = $Application -split " "
Write-Host "Split comparisson App into: $ApplicationSplit"
# Check if the current part is contained within string1
if ($ProductSplit -notcontains $ApplicationSplit) {
# If the current part is not contained within string1, set the flag to false
$allPartsMatch = $false
}
}
# Display a message indicating the result of the comparison
if ($allPartsMatch) {
Write-Host "==============================="
Write-Host "$Application is contained within $Product"
Write-Host "==============================="
$MatchingApps += #{Product01 = $Product; Product02 = $Application}
} else {
#Write-Host "$Application is not contained within $Product"
}
}
However, I seem to have a logic error in my thought process as this returns 0 matches. So obviously, the script isn't properly splitting or comparing the split items.
My question is - how do compare the parts of both app names to see if I have the apps in both csvs? Can I use a specific regex for that or do I need to approach the problem differently?
Cheers,
Fred
I tried comparing both csv files for similar product names. I expected a table of similar product names. I received nothing.
The basis for "matching" one string to another is that they share a prefix - so start by writing a small function that extracts the common prefix of 2 strings, we'll need this later:
function Get-CommonPrefix {
param(
[string]$A,
[string]$B
)
# start by assuming the strings share no common prefix
$prefixLength = 0
# the maximum length of the shared prefix will at most be the length of the shortest string
$maxLength = [Math]::Min($A.Length, $B.Length)
for($i = 0; $i -lt $maxLength; $i++){
if($A[$i] -eq $B[$i]){
$prefixLength = $i + 1
}
else {
# we've reached an index with two different characters, common prefix stops here
break
}
}
# return the shared prefix
return $A.Substring(0, $prefixLength)
}
Now we can determine the shared prefix between two strings:
PS ~> $sharedPrefix = Get-CommonPrefix 'Adobe Acrobat Reader DC Continuous MUI' 'Adobe Acrobat Reader DC v2022.002.20191'
PS ~> Write-Host "The shared prefix is '$sharedPrefix'"
The shared prefix is 'Adobe Acrobat Reader DC '
Now we just need to put it to use in your nested loop:
# Import the first list
$Products01 = Import-CSV 'C:\Temp\ProductsList01.csv' -Delimiter ";"
# Import the second list
$Products02 = Import-CSV 'C:\Temp\ProductList02.csv' -Delimiter ";"
# now let's find the best match from list 2 for each item in list 1:
foreach($productRow in $Products01) {
# we'll use this to keep track of shared prefixes encountered
$matchDetails = [pscustomobject]#{
Index = -1
Prefix = ''
Product2 = $null
}
for($i = 0; $i -lt $Products02.Count; $i++) {
# for each pair, start by extracting the common prefix and see if we have a "better match" than previously
$commonPrefix = Get-CommonPrefix $productRow.Product $Products02[$i].Product
if($commonPrefix.Length -gt $matchDetails.Prefix.Length){
# we found a better match!
$matchDetails.Index = $i
$matchDetails.Prefix = $commonPrefix
$matchDetails.Product2 = $Products02[$i]
}
}
if($matchDetails.Index -ge 0){
Write-Host "Best match found for '$($productRow.Product)': '$($matchDetails.Product2.Product)' "
# put code that needs to work on both rows here ...
}
}
Note: in cases where multiple entries in the second list matches the same-length prefix from the first list, the code simply picks the first match.

PowerShell Table column to string delimited with ','

I am trying to convert values under 'key' column to a single string delimited with ','
$TheTable = (get-command get-mailbox).Parameters
Command returns:
Key Value
--- -----
ErrorAction System.Management.Automation.ParameterMetadata
IncludeInactiveMailbox System.Management.Automation.ParameterMetadata
Verbose System.Management.Automation.ParameterMetadata
OutVariable System.Management.Automation.ParameterMetadata
I am trying to achieve:
$TheTable = "ErrorAction,IncludeInactiveMailbox,Verbose,OutVariable"
I am completely lost as everything I attempt (foreach loop, .ToString) returns:
System.Collections.Generic.Dictionary`2[System.String,System.Management.Automation.ParameterMetadata],
Is there any way too do that?
To get a hashtable's / dictionary's keys, use its .Keys property.
To convert a collection of strings to a single string with a separator, use the -join operator.
Therefore:
$TheTable = (get-command get-mailbox).Parameters.Keys -join ","

Find first available serial based on list of strings?

Given a list of strings such as: apple01, apple02, and apple04, banana02, cherry01, how would you come up with the first available serial number of each type -- that is, apple03 if I ask about apple, or banana01 if I ask about banana, and cherry02 if I ask about cherry?
I'm tasked with automating the creation of Azure VM's, and these strings are actually host names of existing VM's, as reported by the Azure Powershell command (Get-AzureRmResourceGroupDeployment -ResourceGroupName "$azureResGrpName2").DeploymentName (or anything effectively similar).
Update: Here's my working code:
$rgdNames = (Get-AzureRmResourceGroupDeployment -ResourceGroupName "$azureResGrpName").DeploymentName
$siblings = $rgdNames | Where-Object{$_ -match "^($hostname)(\d+)$" }
if ($siblings) {
# Make a list of all numbers in the list of hostnames
$serials = #()
foreach ($sibling in $siblings) {
# $sibling -split ($sibling -split '\d+$') < split all digits from end, then strip off everything at the front
# Then convert it to a number and add that to $serials
$hostnumber = [convert]::ToInt32([string]$($sibling -split ($sibling -split '\d+$'))[1], 10)
$serials += $hostnumber
}
foreach ($i in 1..$siblingsMax){ # Iterate over all valid serial numbers
if (!$serials.Contains($i)) { # Stop when we find a serial number that isn't in the list of existing hosts
$serial = $i
break
}
}
} else {
$serial = 1
}
This seems to do the trick!
## Create variable to store number in
$spareNumber = $null
## we are presuming that they have already been seperated into groups
$apples = #("apples01","apples002","apples3","apples15")
## create an empty array
$num = #()
## You know what a foreach is right? ;)
foreach ($apple in $apples)
{
## the hard working part
## [convert]:: toint32 converts to, you guessed it... (and adds it to $num)
## $apple -split ($apple -split '\d+$') < split all digits from end, then strip off everything at the front
$num += [convert]::ToInt32([string]$($apple -split ($apple -split '\d+$'))[1], 10)
}
## count from 1 to 10, and pass to foreach
(1..10) | foreach {
##'when we find one that isn't in $num, store it in sparenumber and break out of this joint.
if (!$num.Contains($_)) {
$spareNumber = $_
break
}
}
## and here is your number...
$spareNumber
The naive and straight-forward solution would be based on pre-generating a list of syntax valid names.
Query names that are already in use and store the results into a collection.
Iterate the used names collection and remove those from the pre-generated collection.
Sort the pre-generated collection.
Now you got a collection that contains unused names in sorted order. Pick any number of desired names for further usage.
This could be the start of what you are looking for. I took a very similar approach as PetSerAl has done in his comment. I have made mine more verbose as it helps to take in what is happening here. Most of the explanation comes from comments in the code.
# Array of fruits/index values. Using string array as proof on concept.
$fruits = "apple01","apple02","apple04","banana02","cherry01"
# Regex -match the fruit name and index number. This also filters out lines that do not match the standard.
$fruityArray = $fruits | Where-Object{$_ -match '^(.+?)(\d+)$' } | ForEach-Object{
# Create a simple object that splits up the matched info into a fruit index object
[pscustomobject][ordered]#{
Fruit = $matches[1]
Index = [int]$matches[2]
}
}
# Group by fruit and then we can find the next available index within the groups
$fruityArray | Group-Object Fruit | ForEach-Object{
# For this fruit determine the next available index
$thisFruitGroup = $_
# Determine the highest index value. We add one in case all indexes are present from 1 to highest
$highestPossibleIndex = ($thisFruitGroup.Group.Index | Measure-Object -Maximum).Maximum + 1
# Check all possible indexes. Locating all that are free but filter the first one out
$nextAvailableIndex = 1..$highestPossibleIndex | Where-Object{$_ -notin $thisFruitGroup.Group.Index} | Select -First 1
# Take the fruit and add a padded index then pass that value to the pipeline.
'{0}{1:00}'-f $thisFruitGroup.Name, $nextAvailableIndex
}
We take the array of fruits and create an object array of fruit and indexes. Group those together by fruit and then determine the next available index based on all available indexes for that fruit. We add one to the highest possible index on the chance that they are all in use (no gaps). This is the case for cherry.
apple03
banana01
cherry02
Alternatively you could output the results to a variable and call the fruit you need from there if you don't need the whole list as output.
# Group by fruit and then we can find the next available index within the groups
$availableIndexes = $fruityArray | Group-Object Fruit | ForEach-Object{
# For this fruit determine the next available index
$thisFruitGroup = $_
# Determine the highest index value. We add one in case all indexes are present from 1 to highest
$highestPossibleIndex = ($thisFruitGroup.Group.Index | Measure-Object -Maximum).Maximum + 1
# Check all possible indexes. Locating all that are free but filter the first one out
$nextAvailableIndex = 1..$highestPossibleIndex | Where-Object{$_ -notin $thisFruitGroup.Group.Index} | Select -First 1
# Take the fruit and add a padded index then pass that value to the pipeline.
[pscustomobject][ordered]#{
Fruit = $thisFruitGroup.Name
Index = $nextAvailableIndex
String = '{0}{1:00}'-f $thisFruitGroup.Name, $nextAvailableIndex
}
}
$availableIndexes | Where-Object{$_.Fruit -eq "Apple"} | Select-Object -ExpandProperty String
Which would net the output of:
apple03
if the name that occurs first is pure letters not including any number and than followed by a number . that would be easy and you can get the index of the first number or char that exists in that char list {1,2,3,4,5,6,7,8,9}
and than you can make substring(0,indexof(firstnumber))

Split a string containing fixed length columns

I got data like this:
3LLO24MACT01 24MOB_6012010051700000020100510105010 123456
It contains different values for different columns when I import it.
Every column is fixed width:
Col#1 is the ID and just 1 long. Meaning it is "3" here.
Col#2 is 3 in length and here "LLO".
Col#3 is 9 in length and "24MACT01 " (notice that the missing ones gets filled up by blanks).
This goes on for 15 columns or so...
Is there a method to quickly cut it into different elements based on sequence length? I couldn't find any.
This can be done with RegEx matching, and creating an array of custom objects. Something like this:
$AllRecords = Get-Content C:\Path\To\File.txt | Where{$_ -match "^(.)(.{3})(.{9})"} | ForEach{
[PSCustomObject]#{
'Col1' = $Matches[1]
'Col2' = $Matches[2]
'Col3' = $Matches[3]
}
}
That will take each line, match by how many characters are specified, and then create an object based off those matches. It collects all objects in an array and could be exported to CSV or whatever. The 'Col1', 'Col2' etc are just generic column headers I suggested due to a lack of better information, and could be anything you wanted.
Edit: Thank you iCodez for showing me, perhaps inadvertantly, that you can specify a language for your code samples!
[Regex]::Matches will do this rather easily. All you need to do is specify a Regex pattern that has . followed by the number of characters you want in curly braces. For example, to match a column of three characters, you would write .{3}. You then do this for all 15 columns.
To demonstrate, I will use a string that contains the first three columns of your example data (since I know their sizes):
PS > $data = '3LLO24MACT01 '
PS > $pattern = '(.{1})(.{3})(.{9})'
PS > ([Regex]::Matches($data, $pattern).Groups).Value
3LLO24MACT01
3
LLO
24MACT01
PS >
Note that the first value outputted will be the text matched be all of the capture groups. If you do not need this, you can remove it with slicing:
$columns = ([Regex]::Matches($data, $pattern).Groups).Value
$columns = $columns[1..$columns.Length]
New-PSObjectFromMatches is a helper function for creating PS Objects from regex matches.
The -Debug option can help with the process of writing the regex.

PowerShell Split a String On First Occurrence of Substring/Character

I have a string that I want to split up in 2 pieces. The first piece is before the comma (,) and the second piece is all stuff after the comma (including the commas).
I already managed to retrieve the first piece before the comma in the variable $Header, but I don't know how to retrieve the pieces after the first comma in one big string.
$string = "Header text,Text 1,Text 2,Text 3,Text 4,"
$header = $string.Split(',')[0] # $Header = "Header text"
$content = "Text 1,Text 2,Text 3,Text 4,"
# There might be more text then visible here, like say Text 5, Text 6, ..
PowerShell's -split operator supports specifying the maximum number of sub-strings to return, i.e. how many sub-strings to return. After the pattern to split on, give the number of strings you want back:
$header,$content = "Header text,Text 1,Text 2,Text 3,Text 4," -split ',',2
Try something like :
$Content=$String.Split([string[]]"$Header,", [StringSplitOptions]"None")[1]
As you split according to a String, you are using a different signature of the function split.
The basic use needs only 1 argument, a separator character (more info about it can be found here, for instance). However, to use strings, the signature is the following :
System.String[] Split(String[] separator, StringSplitOptions options)
This is why you have to cast your string as an array of string. We use the None option in this case, but you can find the other options available in the split documentation.
Finally, as the value of $Heasder, is at the beggining of your $String, you need to catch the 2nd member of the resulting array.
method of Aaron is the best, but i propose my solution
$array="Header text,Text 1,Text 2,Text 3,Text 4," -split ','
$array[0],($array[1..($array.Length -1)] -join ",")
This alternate solution makes use of PowerShell's ability to distribute arrays to multiple variables with a single assignment. Note, however, that the -split operator splits on every comma and PowerShell's built-in conversion from Array back to String results in the elements being concatenated back together. So it's not as efficient as String.Split, but in your example, it's negligible.
$OFS = ','
$Content = 'Header text,Text 1,Text 2,Text 3,Text 4,'
[String]$Header,[String]$Rest = $Content -split $OFS
$OFS = ' '
Write-Host "Header = $Header"
Write-Host "Rest = $Rest"
Finally, $OFS is a special variable in PowerShell that determines which character will be used when joining the array elements back into a single string. By default, it's a space. But it can be changed to anything.

Resources