I am trying to collect a value per console, in this case an ip address, and that the suffix of this is self-incrementally and multiplied by line up to 254.
I tried with for, but this create each file text. thats my code.
$ipaddress=$args[0]
New-Item .\direcciones.txt -Force
$filetext= New-Item .\direcciones.txt -Force
for ($i=1, $i -le 254;)
{ Add-Content -Path $filetext -Value $ipaddress.$i }
i expect something like the filetext:
192.168.1.1
192.168.1.2
192.168.1.3
...
192.168.1.254
the autoincrement value that i want is the last octet and the 3 first is the $arg
The key is to enclose $ipaddress.$i in "..."; additionally, you can streamline your code:
1..254 | ForEach-Object { "$ipaddress.$_" } | Set-Content $filetext
Or, more efficiently, but more obscurely:
1..254 -replace '^', "$ipaddress." | Set-Content $filetext
As for what you tried:
(This may just be a posting artifact) for ($i=1, $i -le 254;) creates an infinite loop, because you're missing ; ++$i for incrementing the iterator variable $i.
Even in argument-parsing mode, $ipaddress.$i - in the absence of enclosure in "..." - is interpreted as an expression, meaning that $i is interpreted as the name of a property to access on $ipaddress, which therefore results in $null, so that no data is written to the output file.
Only inside an expandable (double-quoted) string ("...") - i.e. "$ipaddress.$i" in this case - are $ipaddress and $i expanded individually.
Related
So I have a Pipe that will search a file for a specific stream and if found will replace it with a masked value, I am trying to have a counter for all of the times the oldValue is replaced with the newValue. It doesn't necessarily need to be a one liner just curious how you guys would go about this. TIA!
Get-Content -Path $filePath |
ForEach-Object {
$_ -replace "$oldValue", "$newValue"
} |
Set-Content $filePath
I suggest:
Reading the entire input file as a single string with Get-Content's -Raw switch.
Using -replace / [regex]::Replace() with a script block to determine the substitution text, which allows you to increment a counter variable every time a replacement is made.
Note: Since you're replacing the input file with the results, be sure to make a backup copy first, to be safe.
In PowerShell (Core) 7+, the -replace operator now directly accepts a script block that allows you to determine the substitution text dynamically:
$count = 0
(Get-Content -Raw $filePath) -replace $oldValue, { $newValue; ++$count } |
Set-Content -NoNewLine $filePath
$count now contains the number of replacements, across all lines (including multiple matches on the same line), that were performed.
In Windows PowerShell, direct use of the underlying .NET API, [regex]::Replace(), is required:
$count = 0
[regex]::Replace(
'' + (Get-Content -Raw $filePath),
$oldValue,
{ $newValue; ++(Get-Variable count).Value }
) | Set-Content -NoNewLine $filePath
Note:
'' + ensures that the call succeeds even if file $filePath has no content at all; without it, [regex]::Replace() would complain about the argument being null.
++(Get-Variable count).Value must be used in order to increment the $count variable in the caller's scope (Get-Variable can retrieve variables defined in ancestral scopes; -Scope 1 is implied here, thanks to PowerShell's dynamic scoping). Unlike with -replace in PowerShell 7+, the script block runs in a child scope.
As an aside:
For this use case, the only reason a script block is used is so that the counter variable can be incremented - the substitution text itself is static. See this answer for an example where the substitution text truly needs to be determined dynamically, by deriving it from the match at hand, as passed to the script block.
Changing my answer due to more clarifications in comments. The best way I can think of is to get the count of the $Oldvalue ahead of time. Then replace!
$content = Get-Content -Path $filePath
$toBeReplaced = Select-String -InputObject $content -Pattern $oldValue -AllMatches
$replacedTotal = $toBeReplaced.Matches.Count
$content | ForEach-Object {$_ -replace "$oldValue", "$newValue"} | Set-Content $filePath
I am trying to change the domains of emails inside a text file for example "john#me.com to john#gmail.com". The emails are stored in a array and I am currently using a for loop with the replace method but I cannot get it to work. Here is the code that I have so far.
$folders = #('Folder1','Folder2','Folder3','Folder4','Folder5')
$names = #('John','Mary','Luis','Gary', 'Gil')
$emails = #("John#domain.com", "Mary#domain.com", "Luis#domain.com", "Gary#domain.com", "Gil#domain.com")
$emails2 = #("John#company.com", "Mary#company.com", "Luis#company.com", "Gary#company.com", "Gil#comapny.com")
$content = "C:\Users\john\Desktop\contact.txt"
#create 10 new local users
foreach ($user in $users){ New-LocalUser -Name $user -Description "This is a test account." -NoPassword }
#Create 5 folders on desktop
$folders.foreach({New-Item -Path "C:\Users\John\Desktop\$_" -ItemType directory})
#create 5 folders on in documents
$folders.foreach({New-Item -Path "C:\users\john\Documents\$_" -ItemType directory})
#create contact.tct
New-Item -Path "C:\Users\John\Desktop" -Name "contact.txt"
#add 5 names to file
ForEach($name in $names){$name | Add-Content -Path "C:\Users\John\Desktop\contact.txt"}
#add 5 emails to file
ForEach($email in $emails){$email | Add-Content -Path "C:\Users\John\Desktop\contact.txt"}
#change emails to #comapny.com
for($i = 0; $i -lt 5; $i++){$emails -replace "$emails[$i]", $emails2[$i]}
In your particular example, you want to replace one string with another in each of your array elements. You can do that without looping:
$emails = $emails -replace '#domain\.com$','#company.com'
Since -replace uses regex matching, the . metacharacter must be escaped to be matched literally. In your case it probably does not matter since . matches any character, but for completeness, you should escape it.
Using the .NET Regex class method Escape(), you can programmatically escape metacharacters.
$emails -replace [regex]::Escape('#domain.com'),'#company.com'
With your code, in order to update $emails, you need to interpolate your array strings properly and update your variable on each loop iteration:
for($i = 0; $i -lt 5; $i++) {
$emails = $emails -replace $emails[$i], $emails2[$i]
}
$emails # displays the updates
If $emails contains other regex metacharacters besides just the single ., it could be another reason why you are having matching issues. It would then just be easiest to escape the metacharacters:
for($i = 0; $i -lt 5; $i++) {
$emails = $emails -replace [regex]::Escape($emails[$i]), $emails2[$i]
}
$emails # displays the updates
Explanation:
When double quotes are parsed (if not inside a verbatim string), the parser will do string expansion. When this happens to variables references that include operators, only the variables are expanded and the rest of the quoted expression including the operator characters is treated as a verbatim string. You can see this with a trivial example:
$str = 'my string 1','my string 2'
"$str[0]"
Output:
my string 1 my string 2[0]
To get around this behavior, you either need to not use quotes around the expression or use the sub-expression operator $():
$str[0]
"$($str[0])"
Note that a quoted array reference will convert the array into a string. Each element of that array will be separated based on the $OFS value (single space by default) of your environment.
I receive a text file with a multiple lists like shown below (edit: more accurate example dataset included)
# SYSTEM X
# SINGULAR
192.168.1.3
# SUB-SYSTEM V
192.168.1.4
192.168.1.5
192.168.1.6
# SYSTEM Y
# MANDATORY
192.168.1.7
192.168.1.8
192.168.1.9
192.168.1.7
192.168.1.8
192.168.1.9
Each "SYSTEM comment" means its a new set after it.
I want to read each block of content separately so each set should be assigned to an object discarding the embedded comments. I just need the IPs.
Something like:
$ipX = get-content -path [file.txt] [set X]
$ipY = get-content -path [file.txt] [set Y]
$ipZ = get-content -path [file.txt] [set Z]
But I'm not sure how to actually assign these sets separately.
Help please.
Here's one possible solution. The result will be a hashtable, each key containing any array of ips for the set:
$result = #{}
get-content file.txt | foreach {
if ($_ -match "#\s*SET\s+(\w+)") {
$result[($key = $matches.1)] = #()
}
elseif ($_ -notlike "#*") {
$result[$key] += $_
}
}
Contents of $result:
Name Value
---- -----
Y {[ip], [ip], [more ips]}
Z {[ip], [ip], [more ips]}
X {[ip], [ip], [more ips]}
Here's another approach. We will take advantage of Foreach-Object's -End block to [PSCustomObject] the final one.
Get-Content $file | Foreach-Object {
if($_ -match 'SET (.+?)'){
if($ht){[PSCustomObject]$ht}
$ht = [ordered]#{Set = $Matches.1}
}
if($_ -match '^[^#]'){
$ht["IPs"] += $_
}
} -End {if($ht){[PSCustomObject]$ht}}
Output
Set IPs
--- ---
X [ip][ip][more ips]
Y [ip][ip][more ips]
Z [ip][ip][more ips]
If you want to also ensure $ht is empty to start with you could use the -Begin block.
Get-Content $file | Foreach-Object -Begin{$ht=$null}{
if($_ -match 'SET (.+?)'){
if($ht){[PSCustomObject]$ht}
$ht = [ordered]#{Set = $Matches.1}
}
if($_ -match '^[^#]'){
$ht["IPs"] += $_
}
} -End {if($ht){[PSCustomObject]$ht}}
You can use Select-String to extract a specific section of text:
# Update $section to be the set you want to target
$section = 'Set Y'
Get-Content a.txt -Raw |
Select-String -Pattern "# $section.*\r?\n(?s)(.*?)(?=\r?\n# Set|$)" | Foreach-Object
{$_.Matches.Groups[1].Value}
Using Get-Content with -Raw reads in the file as a single string making multi-line matching easier. With PowerShell 7, Select-String includes a -Raw switch making this process a bit simpler.
This outputs capture group 1 results, which match the (.*?). If you want to capture between comments rather than between Set <something> and Set <something>, you can edit the -Pattern value at the end to only be # rather than # Set.
Regex Breakdown:
# matches the characters # literally
$section substitutes your variable value matches the value literally provided there are no regex characters in the string
.* matches any character (except for line terminators)
\r matches a carriage return
? Quantifier — Matches between zero and one times, as many times as
possible, giving back as needed (greedy)
\n matches a line-feed (newline) character
(?s) modifier: single line. Dot matches newline characters
1st Capturing Group (.*?)
.*? matches any characters lazily
Positive Lookahead (?=\r?\n# Set)
\r? matches a carriage return zero or more times
\n matches a line-feed (newline) character
# Set matches the characters # Set literally
$ matches the end of the string
If I understand the question with the new example correctly, you want to parse out the file and create single variables of that each holding an array ip IP addresses.
If that is the case, you could do:
# loop through the file line-by-line
$result = switch -Regex -File 'D:\Test\thefile.txt' {
'#\sSYSTEM\s(\w+)' {
# start a new object, output the earlier object if available
if ($obj) { $obj }
$obj = [PsCustomObject]#{ 'System' = $Matches[1]; 'Ip' = #() }
}
'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' {
# looks like an IPv4 address. Add it to the Ip property array of the object
$obj.Ip += $_
}
default {}
}
Now you have an array ob objects in $result:
System Ip
------ --
Y {192.168.1.7, 192.168.1.8, 192.168.1.9, 192.168.1.7...}
X {192.168.1.3, 192.168.1.4, 192.168.1.5, 192.168.1.6}
To make separate variables of that is as easy as:
$ipX = ($result | Where-Object { $_.System -eq 'X' }).Ip
$ipY = ($result | Where-Object { $_.System -eq 'Y' }).Ip
$ipZ = ($result | Where-Object { $_.System -eq 'Z' }).Ip
Your example has duplicate IP addresses. If you don't want these do
$ipX = ($result | Where-Object { $_.System -eq 'X' }).Ip | Select-Object -Unique (same for the others)
I want to create a New-Item in the registry with a pre check what already exists.
This code
$items = get-item "HKCU:\SOFTWARE\Microsoft\Office\16.0\Excel\Options"
$items.Property -match "OPEN"
returns the following
OPEN
OPEN1
OPEN2
OPEN3
OPEN4
Now I know I need to create a New-Item with the name OPEN5, but how do I count through this? Maybe with a While-Loop?
The most robust approach is to extract the embedded numbers, sort them numerically, and add 1 to the highest index to date:
$nextNdx = 1 +
([int[]] ($items.Property -match '^OPEN' -replace '\D') | Sort-Object)[-1]
$items.Property -match '^OPEN' -replace '\D' returns all property names that start with OPEN and removes all non-digit characters from them (-replace '\D').
[int[]] converts the resulting "number strings" to actual numbers ([int]); note that casting '' or $null to [int] in PowerShell yields 0.
Sort-Object sorts these numbers, and [-1] grabs the last number from the resulting array, i.e., the highest number.
The above is convenient, but not fast, due to use of the pipeline and the Sort-Object cmdlet.
If you want to avoid the pipeline for performance reasons:
$indices = [int[]] ($items.Property -match '^OPEN' -replace '\D')
[Array]::Sort($indices) # sort in place
$nextNdx = 1 + $indices[-1]
I would like to retrieve some numbers from a text file using Windows PowerShell. Assume that I have a text file values.txt that looks like this (I can modify it as I want):
foo=100
bar=-3
foobar=-15
asdf=-4
I would like to add a variable to my PowerShell script called $bar that is equal to the number -3, as the text file says it should be. How do I do this?
Note: The input file looks like part of an *.ini file; for more full-featured support of such files, consider a third-party module such as PSIni.
As Matt suggests in a comment on the question, ConvertFrom-StringData sounds like the right tool:
# Read the key-value pairs stored in file file.txt into [hashtable] $ht
PS> ($ht = Get-Content -Raw -LiteralPath file.txt | ConvertFrom-StringData)
Name Value
---- -----
foo 100
bar -3
foobar -15
asdf -4
# Access a specific value:
PS> $ht.foo # or: $ht['foo']
100
Note:
ConvertFrom-StringData returns a hashtable rather than creating individual variables:
As shown above, you must access the foo input line's value as $ht.foo (or $ht['foo']) rather than $foo, for instance.
To ensure that only a single hashtable is created, Get-Content -Raw (PSv3+) must be used to pass the entire input file as a single string.
ConvertFrom-StringData only ever creates [string] values, so if the input values should be treated as numbers, for instance, manual conversion is required:
# .Clone() is needed to support enumeration and modification in the same loop.
foreach($key in $ht.Keys.Clone()) { $ht.$key = [int] $ht.$key }
Generally, there are more subtleties to consider, such as \ rather than ` serving as the escape character and quotes getting retained as literals - see the docs.
This should work:
$file = "C:\test.txt"
foreach($line in (Get-Content $file)) {
$a = $line.Split("=")
New-Variable -Name $a[0] -Value $a[1]
}
Write-Output ""
Write-Output "Variable Check ::"
Write-Output "foo = $foo"
Write-Output "bar = $bar"
Write-Output "foobar = $foobar"
Write-Output "asdf = $asdf"
You can also put the name/value in different variables if you don't want to use an array, just change the for loop:
$a,$b = $line.Split("=")
New-Variable -Name $a -Value $b
Using Matts suggestion of ConvertFrom-StringData :
$line = $line | ConvertFrom-StringData
New-Variable -name $line.Keys -Value $line.Values
hope this helps