Powershell search and replace part of a string - powershell

I have a file with the following content:
123
456
789
XYZ
ABC999XXXXXXX
I need to search the file for 3 numeric characters, and replace the first and third character based on user input. If the user inputs character 1 = 0 and character 3 = 9, I need to return.
029
059
089
I'm trying to do this with a simple search and replace, without creating a variable for each character. Also of note, I need to search for the criteria of 3 numbers, discarding the alpha character rows.
Please Note: This is a simplified version of what I need to do. The situation is long with many more fields but I hope that boiling this down will give me something that I can work with. Thanks in advance.

Assuming that the conditions are as you've described, the -replace operation suggested in the comment should do just what you want.
All you need to do is take user input and interpolate it into the replace string, exemplified below:
# Get user input for the first digit
do{
$a = Read-Host -Prompt "Input 1st digit"
} while ($a -notmatch "^\d$")
# Get user input for the third digit
do{
$b = Read-Host -Prompt "Input 3rd digit"
} while ($b -notmatch "^\d$")
# pattern that matches exactly 3 digits, captures the middle one
$pattern = "^\d(\d)\d$"
# replacement consisting of the user input and a reference to the capture group
$replace = "$a{0}$b" -f '${1}'
# Let's replace!
$InputObject = Get-Content "C:\my\file\path.txt"
$InputObject -replace $pattern,$replace

Related

question about powershell text manipulation

I apologise for asking the very basic question as I am beginner in Scripting.
i was wondering why i am getting different result from two different source with the same formatting. Below are my sample
file1.txt
Id Name Members
122 RCP_VMWARE-DMZ-NONPROD DMZ_NPROD01_111
DMZ_NPROD01_113
123 RCP_VMWARE-DMZ-PROD DMZ_PROD01_110
DMZ_PROD01_112
124 RCP_VMWARE-DMZ-INT.r87351 DMZ_TEMPL_210.r
DMZ_DECOM_211.r
125 RCP_VMWARE-LAN-NONPROD NPROD02_20
NPROD03_21
NPROD04_22
NPROD06_24
file2.txt
Id Name Members
4 HPUX_PROD HPUX_PROD.3
HPUX_PROD.4
HPUX_PROD.5
i'm trying to display the Name column and with this code i'm able to display the file1.txt correctly.
PS C:\Share> gc file1.txt |Select-Object -skip 1 | foreach-object { $_.split(" ")[1]} | ? {$_.trim() -ne "" }
RCP_VMWARE-DMZ-NONPROD
RCP_VMWARE-DMZ-PROD
RCP_VMWARE-DMZ-INT.r87351
RCP_VMWARE-LAN-NONPROD
However with the file2 im getting a different output.
PS C:\Share> gc .\file2.txt |Select-Object -skip 1 | foreach-object { $_.split(" ")[1]} | ? {$_.trim() -ne "" }
4
changing the code to *$_.split(" ")[2]}* helps to display the output correctly
However, i would like to have just 1 code which can be apply for both situation.appreciate if you can help me to sort this.. thank you in advance...
This happens because the latter file has different format.
When examined carefully, one notices there are two spaces between 4 and HPUX_PROD strings:
Id Name Members
4 HPUX_PROD HPUX_PROD.3
^^^^
On the first file, there is a single space between number and string:
Id Name Members
122 RCP_VMWARE-DMZ-NONPROD DMZ_NPROD01_111
^^^
As how to fix the issue depends if you need to match both file formats, or if the other has simply a typing error.
The existing answers are helpful, but let me try to break it down conceptually:
.Split(" ") splits the input string by each individual space character, whereas what you're looking for is to split by runs of (one or more) spaces, given that your column values can be separated by more than one space.
For instance 'a b'.split(' ') results in 3 array elements - 'a', '', 'b' - because the empty string between the two spaces is considered an element too.
The .NET [string] type's .Split() method is based on verbatim strings or character sets and therefore doesn't allow you to express the concept of "one ore more spaces" as a split criterion, whereas PowerShell's regex-based -split operator does.
Conveniently, -split's unary form (see below) has this logic built in: it splits each input string by any nonempty run of whitespace, while also ignoring leading and trailing whitespace, which in your case obviates the need for a regex altogether.
This answer compares and contrasts the -split operator with string type's .Split() method, and makes the case for routinely using the former.
Therefore, a working solution (for both input files) is:
Get-Content .\file2.txt | Select-Object -Skip 1 |
Foreach-Object { if ($value = (-split $_)[1]) { $value } }
Note:
If the column of interest contains a value (at least one non-whitespace character), so must all preceding columns in order for the approach to work. Also, column values themselves must not have embedded whitespace (which is true for your sample input).
The if conditional both extracts the 2nd column value ((-split $_)[1]) and assigns it to a variable ($value = ), whose value then implicitly serves as a Boolean:
Any nonempty string is implicitly $true, in which case the extracted value is output in the associated block ({ $value }); conversely, an empty string results in no output.
For a general overview of PowerShell's implicit to-Boolean conversions, see this bottom section of this answer.
Since this sort-of looks like csv output with spaces as delimiter (but not quite), I think you could use ConvertFrom-Csv on this:
# read the file as string array, trim each line and filter only the lines that
# when split on 1 or more whitespace characters has more than one field
# then replace the spaces by a comma and treat it as CSV
# return the 'Name' column only
(((Get-Content -Path 'D:\Test\file1.txt').Trim() |
Where-Object { #($_ -split '\s+').Count -gt 1 }) -replace '\s+', ',' |
ConvertFrom-Csv).Name
Shorter, but because you are only after the Name column, this works too:
((Get-Content -Path 'D:\Test\file2.txt').Trim() -replace '\s+', ',' | ConvertFrom-Csv).Name -ne ''
Output for file1
RCP_VMWARE-DMZ-NONPROD
RCP_VMWARE-DMZ-PROD
RCP_VMWARE-DMZ-INT.r87351
RCP_VMWARE-LAN-NONPROD
Output for file2
HPUX_PROD

Powershell: combine contents of strings with dots inbetween, ignore empty ones

Our naming convention consists of the first name, insertion, and lastname, all separated by dots. An example:
Stack Overflow = Stack.Overflow
Stack over Flow = Stack.over.flow
These outputs will be used later on in the script for the creation of a mailbox, user account, etc.
I've successfully combined the values of all strings by simply plus-ing them together, like this:
$Convention = $Firstname+"."+$Insertion+"."+$LastName
The values for these strings come from information being put in when the stript runs (Read-Host "....")
Now, I'm struggling with making this more dynamic. Of course, not every person has an insertion in their name. Using the given example, the current output of $Convention would be "Stack..Overflow", instead of "Stack.Overflow".
My question to you is: how can I filter out both, the $Insertion and the extra dot, when $Insertion is empty? It's most likely something very simple, but I can't seem to figure out what it is.
Thanks in advance for any given help!
Kr,
Robbert
I would do
$Convention = ('{0}.{1}.{2}' -f $Firstname, $Insertion, $LastName) -replace '\.+', '.'
The -replace uses regex in the first parameter, so '\.+', '.' means to replace 1 or more consecutive dots by a single dot.
Alternatively you could use regex \.{2,} which reads two or more consecutive dots
Example:
$Firstname = 'Robbert'
$Insertion = ''
$LastName = 'Verwaart'
$Convention = ('{0}.{1}.{2}' -f $Firstname, $Insertion, $LastName) -replace '\.+', '.'
Output:
Robbert.Verwaart
The code below will go through each of your $convention array, if this is an array, and test if the insertion is empty. If the $Insertion variable is empty, the $i will remove the $Insertion variable and the extra .. You need to add this into the script as a test, before creating the mailboxes.
foreach ($i in $convention){
if($insertion -eq "" -or $insertion -eq $null) {
$i= $Firstname+"."+$LastName
} else {
continue
}
}

How to select and fill row in form

I've powershell script which should basically fill the empty row in the picture. I'm not able to find solution on how to select that part and fill it with number.
I've tried to find the name with regex but didn't succeed
here is part of the code
$krokPattern = "https://kazdykrokpomaha.ozp.cz/index.php?kroky/index"
$ie.navigate($krokPattern)
while($ie.Busy) { Start-Sleep -Milliseconds 100 }
[regex]$regex = "krok-\d{4}-\d{2}-\d{2}"
$stering = Select-String -Path $krokPattern -Pattern $regex
Image - how it looks like
You can do something like the following with -replace. Just replace the value assigned to $number with whatever value you deem appropriate. However, a proper parser for the language in the file is going to be best.
$regex = [regex]'(?<=type=")[^"]+(?=" name="krok-\d{4}-\d{2}-\d{2}")'
$number = 24
(Get-Content index.html) -replace $regex,$number | Set-Content index.html
Explanation:
Since -replace uses regex matching, we can build off of your current idea. See the following for the $regex breakdown. The goal is to match all characters between the double quotes after type= and before name="krok-####-##-##".
(?<=): Positive Lookbehind
type=": matches the characters type=" literally
[^"]+: matches a single character that is not " one or more times (+).
`(?=): Positive Lookahead
" name="krok-\d{4}-\d{2}-\d{2}": matches literally "krok- followed by 4 digits, a literal -, 2 digits, a literal -, 2 digits, and a final ".
The characters that match $regex are replaced by $number.
See Regex Demo for example and deeper explanation.

Question regarding incrementing a string value in a text file using Powershell

Just beginning with Powershell. I have a text file that contains the string "CloseYear/2019" and looking for a way to increment the "2019" to "2020". Any advice would be appreciated. Thank you.
If the question is how to update text within a file, you can do the following, which will replace specified text with more specified text. The file (t.txt) is read with Get-Content, the targeted text is updated with the String class Replace method, and the file is rewritten using Set-Content.
(Get-Content t.txt).Replace('CloseYear/2019','CloseYear/2020') | Set-Content t.txt
Additional Considerations:
General incrementing would require a object type that supports incrementing. You can isolate the numeric data using -split, increment it, and create a new, joined string. This solution assumes working with 32-bit integers but can be updated to other numeric types.
$str = 'CloseYear/2019'
-join ($str -split "(\d+)" | Foreach-Object {
if ($_ -as [int]) {
[int]$_ + 1
}
else {
$_
}
})
Putting it all together, the following would result in incrementing all complete numbers (123 as opposed to 1 and 2 and 3 individually) in a text file. Again, this can be tailored to target more specific numbers.
$contents = Get-Content t.txt -Raw # Raw to prevent an array output
-join ($contents -split "(\d+)" | Foreach-Object {
if ($_ -as [int]) {
[int]$_ + 1
}
else {
$_
}
}) | Set-Content t.txt
Explanation:
-split uses regex matching to split on the matched result resulting in an array. By default, -split removes the matched text. Creating a capture group using (), ensures the matched text displays as is and is not removed. \d+ is a regex mechanism matching a digit (\d) one or more (+) successive times.
Using the -as operator, we can test that each item in the split array can be cast to [int]. If successful, the if statement will evaluate to true, the text will be cast to [int], and the integer will be incremented by 1. If the -as operator is not successful, the pipeline object will remain as a string and just be output.
The -join operator just joins the resulting array (from the Foreach-Object) into a single string.
AdminOfThings' answer is very detailed and the correct answer.
I wanted to provide another answer for options.
Depending on what your end goal is, you might need to convert the date to a datetime object for future use.
Example:
$yearString = 'CloseYear/2019'
#convert to datetime
[datetime]$dateConvert = [datetime]::new((($yearString -split "/")[-1]),1,1)
#add year
$yearAdded = $dateConvert.AddYears(1)
#if you want to display "CloseYear" with the new date and write-host
$out = "CloseYear/{0}" -f $yearAdded.Year
Write-Host $out
This approach would allow you to use $dateConvert and $yearAdded as a datetime allowing you to accurately manipulate dates and cultures, for example.

How can I replace every comma with a space in a text file before a pattern using PowerShell

I have a text file with lines in this format:
FirstName,LastName,SSN,$x.xx,$x.xx,$x.xx
FirstName,MiddleInitial,LastName,SSN,$x.xx,$x.xx,$x.xx
The lines could be in either format. For example:
Joe,Smith,123-45-6789,$150.00,$150.00,$0.00
Jane,F,Doe,987-65-4321,$250.00,$500.00,$0.00
I want to basically turn everything before the SSN into a single field for the name thus:
Joe Smith,123-45-6789,$150.00,$150.00,$0.00
Jane F Doe,987-65-4321,$250.00,$500.00,$0.00
How can I do this using PowerShell? I think I need to use ForEach-Object and at some point replace "," with " ", but I don't know how to specify the pattern. I also don't know how to use a ForEach-Object with a $_.Where so that I can specify the "SkipUntil" mode.
Thanks very much!
Mathias is correct; you want to use the -replace operator, which uses regular expressions. I think this will do what you want:
$string -replace ',(?=.*,\d{3}-\d{2}-\d{4})',' '
The regular expression uses a lookahead (?=) to look for any commas that are followed by any number of any character (. is any character, * is any number of them including 0) that are then followed by a comma immediately followed by a SSN (\d{3}-\d{2}-\d{4}). The concept of "zero-width assertions", such as this lookahead, simply means that it is used to determine the match, but it not actually returned as part of the match.
That's how we're able to match only the commas in the names themselves, and then replace them with a space.
I know it's answered, and neatly so, but I tried to come up with an alternative to using a regex - count the number of commas in a line, then replace either the first one, or the first two, commas in the line.
But strings can't count how many times a character appears in them without using the regex engine(*), and replacements can't be done a specific number of times without using the regex engine(**), so it's not very neat:
$comma = [regex]","
Get-Content data.csv | ForEach {
$numOfCommasToReplace = $comma.Matches($_).Count - 4
$comma.Replace($_, ' ', $numOfCommasToReplace)
} | Out-File data2.csv
Avoiding the regex engine entirely, just for fun, gets me things like this:
Get-Content .\data.csv | ForEach {
$1,$2,$3,$4,$5,$6,$7 = $_ -split ','
if ($7) {"$1 $2 $3,$4,$5,$6,$7"} else {"$1 $2,$3,$4,$5,$6"}
} | Out-File data2.csv
(*) ($line -as [char[]] -eq ',').Count
(**) while ( #counting ) { # split/mangle/join }